THIS WORK IS YET TO BE REVIEWED

1 Introduction

This paper contains estimates for the reproductive number \(R_{t,m}\) over time \(t\) in various countries \(m\). It also shows the same for provinces within South Africa.. This is done using the methodology as described in [1]. These have been implemented in R using EpiEstim package [2] which is what is used here.

This paper and it’s results should be updated roughly daily and is available online.

2 Updates

As this paper is updated over time this section will summarise significant changes. The code producing this paper is tracked using Git. The Git commit hash for this project at the time of generating this paper was 882600f2cf8d05a50be890458d3213158591870a.

2020-06-12

  • Initial version.

2020-06-13

  • Combine the version for South Africa and other countries.

2020-06-14

  • Add Google Mobility data for comparison and further modelling.

2020-06-15

  • Add more details about methodology.

3 Libraries

The project uses the following libraries.

require(EpiEstim)
require(EnvStats)
require(ggplot2)
require(ggpubr)
require(lubridate)
require(utils)
require(httr)
require(dplyr)
require(tidyr)
require(scales)

4 Countries

All countries available in the data will be analysed but also the provinces of South Africa. This paper produces charts for the following countries only:

  • Brazil
  • Canada
  • Chile
  • India
  • Ireland
  • Italy
  • Peru
  • South Africa
  • Spain
  • United Kingdom
country_codes = c("BR", "CA", "CL", "IN", "IE", "IT", "PE", "ZA", "ES", "UK")

5 Data

5.1 South Africa

5.1.1 Download

Data is downloaded from the Git repository associated with [3].

# Provincial Deaths
GET(url = "https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_deaths.csv", write_disk("covid19za_provincial_cumulative_timeline_deaths.csv", 
    overwrite = TRUE))
Response [https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_deaths.csv]
  Date: 2020-06-16 09:49
  Status: 200
  Content-Type: text/plain; charset=utf-8
  Size: 7.62 kB
<ON DISK>  C:\Users\lrossou\Desktop\COVID-19\rt_estimates\covid19za_provincial_cumulative_timeline_deaths.csv
# Provincial Cases
GET(url = "https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_confirmed.csv", write_disk("covid19za_provincial_cumulative_timeline_confirmed.csv", 
    overwrite = TRUE))
Response [https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_confirmed.csv]
  Date: 2020-06-16 09:49
  Status: 200
  Content-Type: text/plain; charset=utf-8
  Size: 9.59 kB
<ON DISK>  C:\Users\lrossou\Desktop\COVID-19\rt_estimates\covid19za_provincial_cumulative_timeline_confirmed.csv

5.1.2 Data preperation

First read in the data from the downloaded comma-separated values text file.

# Read from CSVs above
data_za_cases <- read.csv("covid19za_provincial_cumulative_timeline_confirmed.csv", stringsAsFactors = FALSE)
data_za_deaths <- read.csv("covid19za_provincial_cumulative_timeline_deaths.csv", stringsAsFactors = FALSE)

In the case data file row 21 and 32 contain no provincial details. We estimated it by spreading the national total to the provinces in proportion to a mixture of the prior day and the next day.

data_za_cases[21, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")] <- colSums(data_za_cases[c(20, 22), c("EC", "FS", "GP", 
    "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")])/sum(data_za_cases[c(20, 22), ]$total) * data_za_cases[21, ]$total
data_za_cases[32, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")] <- colSums(data_za_cases[c(31, 33), c("EC", "FS", "GP", 
    "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")])/sum(data_za_cases[c(31, 33), ]$total) * data_za_cases[32, ]$total

The following function will be applied to case and death data. In this function the following occurs:

  1. Scale up the per province data for unknown values.
  2. This results in provincial data which are not whole numbers. These are rounded to the nearest whole number.
  3. A SA column is added as the sum of the new per province data.
  4. Data is formatted and disaggregated such that item represents the incremental cases or deaths rather than cumulative figures.
  5. Data is filled with data (albeit with 0 cases or deaths) for all dates in the range.
  6. Any incremental case or death counts that are negative are zeroeised.
  7. New cumulative figures are calculated.
fix_data_za <- function(data_za, start_date = as.Date("2020-03-01"), end_date = as.Date("2020-03-31")) {
    # Scale provinces by scale factor (assume unknown are in proportion)
    data_za[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")] <- data_za[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")] * 
        (1 + data_za$UNKNOWN/rowSums(data_za[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")]))
    
    # Only select columns we need
    data_za <- data_za %>% select("date", "EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")
    data_za$date <- as.Date(data_za$date, "%d-%m-%Y")
    
    # Round data so we have integer cases
    data_za[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")] <- round(data_za[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", 
        "WC")], 0)
    
    # Calculate a new SA column
    data_za$SA <- rowSums(data_za[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")])
    
    # 'Melt' the data
    data_za <- pivot_longer(data_za, cols = c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "SA"), names_to = "province", values_to = "count")
    
    # Getting daily data from the cumulative data set
    data_za = data_za %>% group_by(province) %>% arrange(date) %>% mutate(count = count - lag(count, default = 0)) %>% ungroup()
    
    # add missing dates
    all_dates <- expand_grid(date = seq(start_date, end_date, 1), province = levels(as.factor(data_za$province)))
    
    # join
    data_za <- left_join(all_dates, data_za, by = c("date", "province"))
    
    # province factor
    data_za$province <- as.factor(data_za$province)
    
    # 0 for NAs
    data_za$count <- ifelse(is.na(data_za$count), 0, data_za$count)
    
    # remove negatives
    data_za$count <- ifelse(data_za$count < 0, 0, data_za$count)
    
    # get cumulative counts
    data_za <- data_za %>% group_by(province) %>% mutate(cumulative_count = cumsum(count)) %>% ungroup()
    
    return(data_za)
}

Below we use the function above to process deaths and cases and then combine them into a single dataset.

start_date <- min(as.Date(data_za_cases$date, "%d-%m-%Y"))
end_date <- max(as.Date(data_za_cases$date, "%d-%m-%Y"))
data_za_cases <- fix_data_za(data_za_cases, start_date = start_date, end_date = end_date)
data_za_deaths <- fix_data_za(data_za_deaths, start_date = start_date, end_date = end_date)

data_za_cases <- cbind("cases", data_za_cases)
data_za_deaths <- cbind("deaths", data_za_deaths)
colnames(data_za_cases)[1] <- "type"
colnames(data_za_deaths)[1] <- "type"

# combined
data_za <- rbind(data_za_cases, data_za_deaths)

# remove data sets no longer needed
rm("data_za_cases", "data_za_deaths", "start_date", "end_date", "fix_data_za")

5.2 Other countries

5.2.1 Download

Data for other countries are downloaded from [4].

GET(url = "https://opendata.ecdc.europa.eu/covid19/casedistribution/csv", write_disk("ecdc_casedistribution.csv", overwrite = TRUE))
Response [https://opendata.ecdc.europa.eu/covid19/casedistribution/csv/]
  Date: 2020-06-16 09:49
  Status: 200
  Content-Type: application/octet-stream
  Size: 1.39 MB
<ON DISK>  C:\Users\lrossou\Desktop\COVID-19\rt_estimates\ecdc_casedistribution.csv

5.2.2 Data preperation

First read in the data from the downloaded comma-separated values text file.

# Read from CSVs above
data <- read.csv("ecdc_casedistribution.csv", stringsAsFactors = FALSE)

Update data by doing the following:

  1. Format dates.
  2. Set column names.
  3. Add data where dates were skipped (with 0 cases or deaths)
  4. “Melt” the data down.
  5. Set fields as factors as needed.
  6. Get cumulative numbers.
# get date
data$date <- as.Date(data$dateRep, format = "%d/%m/%Y")

# get cases and deaths
data <- data %>% select(continentExp, date, geoId, countriesAndTerritories, cases, deaths)

# column names
colnames(data) <- c("continent", "date", "country_code", "country", "cases", "deaths")

# add 0 data for dates that were skipped
for (c in levels(as.factor(data$country))) {
    c_dates <- (data %>% filter(country == c))$date
    check_dates <- seq(min(c_dates), max(c_dates), by = 1)
    missing_dates <- check_dates[!check_dates %in% c_dates]
    continent <- (data %>% filter(country == c))$continent[1]
    country_code <- (data %>% filter(country == c))$country_code[1]
    if (length(missing_dates) > 0) {
        missing_data <- data.frame(continent = continent, date = missing_dates, country_code = country_code, country = rep(c, length(missing_dates)), 
            cases = rep(0, length(missing_dates)), deaths = rep(0, length(missing_dates)))
        data <- rbind(data, missing_data)
    }
}

# 'Melt' the data
data <- pivot_longer(data, cols = c("cases", "deaths"), names_to = "type", values_to = "count")

# fields as factors
data$continent <- as.factor(data$continent)
data$country_code <- as.factor(data$country_code)
data$country <- as.factor(data$country)
data$type <- as.factor(data$type)

# make sure it is a data frame
data <- as.data.frame(data)

# zeroise counts below 0
data$count <- ifelse(data$count < 0, 0, data$count)

# order and get group by
data <- data %>% group_by(country_code, country, type) %>% arrange(country_code, country, type, date) %>% mutate(cumulative_count = cumsum(count)) %>% 
    ungroup()

5.3 Google Mobility Data

Google released mobility data indexes that deviations from a baseline of movement during 3 January to 6 February 2020 [5]. This data is downloaded and prepared for linking to our estimates of \(R_{t,m}\).

5.3.1 Download

The data is made available as a comma-separated file which is updated regularly but not daily. Data is sometimes up to a week behind.

GET(url = "https://www.gstatic.com/covid19/mobility/Global_Mobility_Report.csv", write_disk("google_mobility_data.csv", overwrite = TRUE))
Response [https://www.gstatic.com/covid19/mobility/Global_Mobility_Report.csv]
  Date: 2020-06-16 09:50
  Status: 200
  Content-Type: text/csv
  Size: 35.6 MB
<ON DISK>  C:\Users\lrossou\Desktop\COVID-19\rt_estimates\google_mobility_data.csv

5.3.2 Preparing the mobility data

First read in the file:

# Read from CSVs above
google_mobility_data <- read.csv("google_mobility_data.csv", stringsAsFactors = FALSE, na.strings = "")

Below the data is prepared by doing the following:

  1. Fixing dates
  2. Retaining only country level mobility data, except for South Africa where country and provincial data is retained.
  3. Renaming the fields consistently.
  4. Various transformations to make joining to other data easier later.
# fix dates
google_mobility_data$date <- as.Date(google_mobility_data$date, format = "%Y-%m-%d")

# keep only country level data but keep all data for ZA
google_mobility_data <- google_mobility_data %>% filter(country_region_code == "ZA" | is.na(sub_region_1))

# pick only the fields required
google_mobility_data <- google_mobility_data %>% select(country_region_code, country_region, sub_region_1, iso_3166_2_code, date, retail_and_recreation_percent_change_from_baseline, 
    grocery_and_pharmacy_percent_change_from_baseline, parks_percent_change_from_baseline, transit_stations_percent_change_from_baseline, workplaces_percent_change_from_baseline, 
    residential_percent_change_from_baseline)

# rename the fields
colnames(google_mobility_data) <- c("country_code", "country", "province", "province_code", "date", "retail_and_recreation", "grocery_and_pharmacy", 
    "parks", "transit_stations", "workplaces", "residential")

# move province data to country for ease later
google_mobility_data$country_code[!is.na(google_mobility_data$province_code)] <- google_mobility_data$province_code[!is.na(google_mobility_data$province_code)]
google_mobility_data$country[!is.na(google_mobility_data$province_code)] <- google_mobility_data$province[!is.na(google_mobility_data$province_code)]

# rename KZN consistently
google_mobility_data$country_code[google_mobility_data$country_code == "ZA-NL"] <- "ZA-KZN"

# drop province columns
google_mobility_data <- google_mobility_data[, c(-3, -4)]

# divide indexes by 100 to have numbers between 0 and 1
google_mobility_data[, 4:9] <- google_mobility_data[, 4:9]/100

# fix GB code to UK
google_mobility_data$country_code[google_mobility_data$country_code == "GB"] <- "UK"

# make factors
google_mobility_data$country_code <- as.factor(google_mobility_data$country_code)
google_mobility_data$country <- as.factor(google_mobility_data$country)

Further to the above we also calculate an average mobility index per [6]. This index averages the mobility indexes but exclude the following indexes:

  • Residential index is excluded as it’s less likely to drive the epidemic (and is inversely correlated),
  • Mobility in parks is also unlikely to be a driving factor.
google_mobility_data$average_mobility <- (google_mobility_data$retail_and_recreation + google_mobility_data$grocery_and_pharmacy + google_mobility_data$transit_stations + 
    google_mobility_data$workplaces)/4

5.4 Combine South African data with other data

To simplify further analysis it’s easier to combine all data in a single dataset:

# Add a 'continent' field to data_za and combine
data_za <- cbind("SA", data_za)
colnames(data_za)[1] <- "continent"
colnames(data_za)[4] <- "country"
data_za$country_code <- paste0("ZA-", data_za$country)
data <- rbind(data, data_za)

# remove all objects except what we need
rm(list = ls()[!ls() %in% c("data", "country_codes", "google_mobility_data")])

6 Basic Exploration

6.1 South Africa

Below we plot cumulative case count on a log scale by province:

ggplot(data %>% filter(continent == "SA" & type == "cases" & cumulative_count > 0), aes(x = date, y = cumulative_count)) + geom_line(aes(color = country), 
    size = 1) + scale_y_log10(labels = comma) + ggtitle("Cumulative Cases by Province") + xlab("Date") + ylab("Cumulative Cases") + theme_bw() + 
    theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + scale_color_hue(l = 50)

Below we plot the cumulative deaths by province on a log scale:

ggplot(data %>% filter(continent == "SA" & type == "deaths" & cumulative_count > 0), aes(x = date, y = cumulative_count)) + geom_line(aes(color = country), 
    size = 1) + scale_y_log10(labels = comma) + ggtitle("Cumulative Deaths by Province") + xlab("Date") + ylab("Cumulative Deaths") + theme_bw() + 
    theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + scale_color_hue(l = 50)

Below we plot average mobility indexes by province:

ggplot(data = google_mobility_data[substr(as.character(google_mobility_data$country_code), 1, 2) == "ZA", ], aes(x = date, y = average_mobility, 
    colour = country)) + geom_line() + scale_color_hue(l = 50) + theme_bw()

6.2 Other Countries

Below we plot cumulative case count on a log scale by country:

ggplot(data %>% filter(continent != "SA" & type == "cases" & country_code %in% country_codes & cumulative_count > 0), aes(x = date, y = cumulative_count)) + 
    geom_line(aes(color = country), size = 1) + scale_y_log10(labels = comma) + ggtitle("Cumulative Cases by Country") + xlab("Date") + ylab("Cumulative Cases") + 
    theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
    scale_color_hue(l = 50)

Below we plot the cumulative deaths by country on a log scale:

ggplot(data %>% filter(continent != "SA" & type == "deaths" & country_code %in% country_codes & cumulative_count > 0), aes(x = date, y = cumulative_count)) + 
    geom_line(aes(color = country), size = 1) + scale_y_log10(labels = comma) + ggtitle("Cumulative Deaths by Country") + xlab("Date") + ylab("Cumulative Deaths") + 
    theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
    scale_color_hue(l = 50)

Below we plot average mobility indexes by country:

ggplot(data = google_mobility_data %>% filter(country_code %in% country_codes), aes(x = date, y = average_mobility, colour = country)) + geom_line() + 
    scale_color_hue(l = 50) + theme_bw()

7 Method & Assumptions

This analysis follows the follows the method proposed [1].

Essentially this models the following relationship:

\(E(I_{t,m})=R_{t,m}\sum_{s=1}^tI_{t-s,m}w_{s}\) where:

  • \(m\) is the country of province in case of South African data.
  • \(I_{t,m}\) is the number of cases (or deaths) reported at time \(t\).
  • \(R_{t,m}\) is the “instantaneous reproduction number” to quote [1].
  • \(I_{t,m} \sim Poisson\) with the above mean.
  • \(w_{s}\) is the infectivity profile of an individual over time \(s\).

The formulation of instantaneous rate of transmission proposed in [1] is convenient because it ensures that \(R_{t,m}\) only depends on information that is known at time \(t\). We do not need to know about future transmission.

7.1 Serial Interval

To do further analysis an serial interval assumption is needed (\(w_{s}\)). The serial interval is taken from [7]. It’s assumed to be Gamma distributed with mean of 6.48 and standard deviation of 3.83. This corresponds to the effective infectiousness of an individual since acquiring the infection themselves.

We plot this serial distribution below:

si_mean <- 6.48
si_sd <- 3.83
si_cv <- si_sd/si_mean

serial_interval = rep(0, 1)
serial_interval[1] = (pgammaAlt(1.5, si_mean, si_cv) - pgammaAlt(0, si_mean, si_cv))
for (i in 2:50) {
    serial_interval[i] = (pgammaAlt(i + 0.5, si_mean, si_cv) - pgammaAlt(i - 0.5, si_mean, si_cv))
}
ggplot(data.frame(days = seq(1, 20, 1), serial_interval = serial_interval[1:20]), aes(y = serial_interval, x = days)) + geom_line(size = 1) + 
    theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
    scale_color_hue(l = 50)

7.2 Time Window Size

# sliding window
tau <- 7

[1] describes a choice of a time window to estimate \(R_{t,m}\). Based on this analysis we chose a time window of 7-days (\(\tau\) in using notation of [1]). During this window \(R_{t,m}\) is assumed to be constant. However information prior that is taken into account.

Note that the time window size does not interact with the serial interval as information prior to the window is taken into account. It’s just the period during which \(R_{t,m}\) is assumed to be constant.

Based on the table in Appendix 2 of [1] we believe a window of 7-days to be reasonable as long as that window contains 12 or more cases or deaths.

8 Estimate \(R_{t,m}\)

8.1 Estimation Routine

The following code works backwards and fits \(R_{t,m}\) values for whole weeks (ending on the last date in the data) using the EpiEstim package. Using deaths or infection the as the cases in the puts the estimate of \(R_{t,m}\) as at the date of the cases being tracked.

So \(t\) is based on the date of reporting (be that cases or deaths) and \(m\) is the country or province. Two values are estimated for each country:

  1. \(R_{t,m}^{cases}\) is the reproductive number implied by the cases reported at time \(t\) in country \(m\).
  2. \(R_{t,m}^{deaths}\) is the reproductive number implied by the deaths reported at time \(t\) in country \(m\).

Note that the time periods are left unadjusted, though in reality the \(R_{t,m}^{deaths}\) should be shifted back approximately 2 weeks relative to \(R_{t,m}^{cases}\).

Rt_data <- NULL
for (c in levels(data$country)) {
    for (t in levels(data$type)) {
        # filter out country data of type t
        c_data <- data %>% filter(country == c & type == t)
        
        # vector of count of cases/deaths
        I <- c_data$count
        
        # t=1 corresponds to this date:
        t1_date <- min(c_data$date)
        
        # only continue if c_data$cumulative_count > 12
        if (max(c_data$cumulative_count) >= 12) {
            # the day after the first case/death:
            t_start_date <- min((c_data %>% filter(cumulative_count >= 12))$date) + 1
            t_start <- min(seq(1, length(I))[c_data$cumulative_count >= 12]) + 1
            
            # last day of cases/deaths
            t_end <- length(I)
            t_end_date <- t1_date + (t_end - 1)
            
            # how many full weeks do we have
            full_weeks <- floor((t_end - t_start)/tau)
            
            # only continue if we have 1 full week or more
            if (full_weeks > 0) {
                # then divide period into weeks
                T.Start <- seq(t_end - tau * full_weeks, t_end - tau, by = tau)
                T.End <- T.Start + tau
                
                # estimate $R_{t,m}^{type}$ for each week:
                c_Rt <- estimate_R(incid = I, config = make_config(t_start = T.Start, t_end = T.End, mean_si = si_mean, std_si = si_sd), method = "parametric_si")$R
                
                # count cases
                c_data$week <- floor(as.numeric(c_data$date - (t1_date + T.Start[1]))/tau)
                c_data$dd <- c(0, c_data$date[2:nrow(c_data)] - c_data$date[1:(nrow(c_data) - 1)])
                c_agg_data <- c_data %>% filter(week >= 0) %>% group_by(week) %>% summarise(count = sum(count), .groups = "drop")
                
                # add country and type designations
                c_Rt <- cbind(c_data$continent[1], c_data$country_code[1], c, t, c_Rt, c_agg_data$count)
                
                # and add dates
                c_Rt$date_start <- t1_date + c_Rt$t_start
                c_Rt$date_end <- t1_date + c_Rt$t_end - 1
                
                # combine the results
                if (is.null(Rt_data)) {
                  Rt_data <- c_Rt
                } else {
                  Rt_data <- rbind(Rt_data, c_Rt)
                }
            }
        }
    }
}

8.2 Prepping data for tabulation and plots

Below we prep column headings and prepare CI data:

# Column names
colnames(Rt_data) <- c("continent", "country_code", "country", "type", "t_start", "t_end", "Rt_mean", "Rt_std", "Rt_li_95", "Rt_li_90", "Rt_li_50", 
    "Rt_median", "Rt_ui_50", "Rt_ui_90", "Rt_ui_95", "count", "date_start", "date_end")

# mid week data point
Rt_data$date_mid <- Rt_data$date_start + (Rt_data$date_end - Rt_data$date_start)/2

# split out CI data into different dataset
Rt_ci_data <- NULL
for (ci in c("50", "90", "95")) {
    r <- data.frame(country_code = Rt_data$country_code, country = Rt_data$country, type = Rt_data$type, ci = rep(paste0(ci, "%"), nrow(Rt_data)), 
        date_mid = Rt_data$date_mid, Rt_mean = Rt_data$Rt_mean, Rt_li = Rt_data[[paste0("Rt_li_", ci)]], Rt_ui = Rt_data[[paste0("Rt_ui_", 
            ci)]])
    Rt_ci_data <- rbind(r, Rt_ci_data)
}

# Factors
Rt_data$type <- as.factor(Rt_data$type)
Rt_data$country <- as.factor(Rt_data$country)

# remove what is not needed
rm(list = ls()[!ls() %in% c("data", "country_codes", "Rt_data", "Rt_ci_data", "google_mobility_data", "tau")])

9 Linking \(R_{t,m}\) estimates to mobility

9.1 Preparing Rt_data

Linking the mobility data should be done in a way that corresponds to the time the infection should have occurred not the time of death. Below new date ranges are calculated to correspond roughly to the time of infection:

Rt_data$date_inf_start <- Rt_data$date_start - ifelse(Rt_data$type == "death", 23 + 7, 6 + 7)
Rt_data$date_inf_end <- Rt_data$date_end - ifelse(Rt_data$type == "death", 23 + 7, 6 + 7)
Rt_data$date_inf_mid <- Rt_data$date_mid - ifelse(Rt_data$type == "death", 23 + 7, 6 + 7)

9.2 Calculating weekly mobility data

The code below calculates average weekly mobility data averaged over the same date ranges as calculated above.

weekly_mobility_data <- NULL
combined_country_codes <- levels(Rt_data$country_code)[levels(Rt_data$country_code) %in% levels(google_mobility_data$country_code)]
for (c in combined_country_codes) {
    for (t in levels(Rt_data$type)) {
        c_data <- Rt_data %>% filter(country_code == c & type == t)
        if (nrow(c_data) > 0) {
            for (i in 1:nrow(c_data)) {
                w_data <- google_mobility_data %>% filter(country_code == c & date >= c_data$date_inf_start[i] & date <= c_data$date_inf_end[i]) %>% 
                  group_by(country_code) %>% summarise(retail_and_recreation = mean(retail_and_recreation), grocery_and_pharmacy = mean(grocery_and_pharmacy), 
                  parks = mean(parks), transit_stations = mean(transit_stations), workplaces = mean(workplaces), residential = mean(residential), 
                  average_mobility = mean(average_mobility), .groups = "drop")
                if (nrow(w_data) > 0) {
                  if (!is.na(w_data$country_code)) {
                    w_data$type <- t
                    w_data$date_inf_mid <- c_data$date_inf_mid[i]
                    weekly_mobility_data <- rbind(weekly_mobility_data, w_data)
                  }
                }
            }
        }
    }
}

9.3 Joining with \(R_{t,m}\) estimates

Based on work above the weekly mobility data can be joined:

Rt_data <- left_join(Rt_data, weekly_mobility_data, by = c("country_code", "type", "date_inf_mid"))

10 Modelling \(R_{t,m}\) as a function of Mobility Indexes

TO DO

11 Results

11.1 Current \(R_{t,m}\) estimates

Below current (last weekly) \(R_{t,m}\) estimates are tabulated. To so the data first needs to be prepared:

# find the last estimates
last_dates <- Rt_data %>% group_by(country, type) %>% summarise(date_mid = max(date_mid), .groups = "drop")

# construct a table with nice fields names
table <- inner_join(last_dates, Rt_data, by = c("country", "type", "date_mid"))
table <- table %>% select(continent, country_code, country, type, count, date_end, Rt_li_95, Rt_mean, Rt_ui_95)

11.1.1 South Africa

knitr::kable((table %>% filter(continent == "SA"))[, c(-1, -2)], format.args = list(big.mark = ","), digits = 1, col.names = c("Country", "Estimated Type", 
    "Count (Last Week)", "Week Ending", "R - Lower CI", "R - Mean", "R - Uppper CI"))
Country Estimated Type Count (Last Week) Week Ending R - Lower CI R - Mean R - Uppper CI
EC cases 4,260 2020-06-15 1.6 1.7 1.7
EC deaths 109 2020-06-15 1.8 2.2 2.6
FS cases 139 2020-06-15 1.2 1.5 1.7
GP cases 5,940 2020-06-15 2.2 2.2 2.3
GP deaths 40 2020-06-15 1.5 2.0 2.7
KZN cases 785 2020-06-15 1.1 1.2 1.3
KZN deaths 7 2020-06-15 0.5 1.0 1.7
LP cases 118 2020-06-15 1.7 2.0 2.3
MP cases 139 2020-06-15 1.5 1.8 2.1
NC cases 87 2020-06-15 1.7 2.1 2.6
NW cases 598 2020-06-15 1.6 1.7 1.8
SA cases 22,653 2020-06-15 1.3 1.4 1.4
SA deaths 488 2020-06-15 1.2 1.4 1.5
WC cases 10,587 2020-06-15 1.0 1.1 1.1
WC deaths 327 2020-06-15 1.1 1.2 1.3

11.1.2 Other Countries

The table below contains \(R_{t,m}\) estimates for the last week available. These estimates are based either on cases or deaths and a 95% confidence interval is shown.

knitr::kable((table %>% filter(continent != "SA" & country_code %in% country_codes))[, c(-1, -2)], format.args = list(big.mark = ","), digits = 1, 
    col.names = c("Country", "Estimated Type", "Count (Last Week)", "Week Ending", "R - Lower CI", "R - Mean", "R - Uppper CI"))
Country Estimated Type Count (Last Week) Week Ending R - Lower CI R - Mean R - Uppper CI
Brazil cases 180,859 2020-06-16 1.0 1.0 1.0
Brazil deaths 6,825 2020-06-16 0.9 0.9 1.0
Canada cases 2,903 2020-06-16 0.7 0.7 0.7
Canada deaths 340 2020-06-16 0.6 0.6 0.7
Chile cases 40,590 2020-06-16 1.1 1.1 1.2
Chile deaths 1,098 2020-06-16 1.0 1.0 1.1
India cases 76,493 2020-06-16 1.1 1.1 1.1
India deaths 2,434 2020-06-16 1.2 1.3 1.3
Ireland cases 114 2020-06-16 0.5 0.6 0.7
Ireland deaths 23 2020-06-16 0.5 0.7 1.0
Italy cases 2,012 2020-06-16 0.9 0.9 1.0
Italy deaths 407 2020-06-16 0.7 0.8 0.9
Peru cases 33,296 2020-06-16 0.9 0.9 0.9
Peru deaths 1,289 2020-06-16 1.1 1.2 1.3
South_Africa cases 22,654 2020-06-16 1.3 1.4 1.4
South_Africa deaths 488 2020-06-16 1.2 1.3 1.5
Spain cases 2,392 2020-06-15 0.9 0.9 1.0
Spain deaths 0 2020-06-15 0.0 0.0 0.1
United_Kingdom cases 9,458 2020-06-16 0.8 0.8 0.8
United_Kingdom deaths 1,139 2020-06-16 0.6 0.6 0.7

11.1.3 Top 10 Countries

Below we show various extremes of \(R_{t,m}\) where counts (deaths or cases) exceed 50 in the last week.

11.1.3.1 Lowest \(R_{t,m}\) based on Deaths

knitr::kable((table %>% filter(continent != "SA" & type == "deaths" & count > 50) %>% arrange(Rt_mean))[1:10, c(-1, -2)], format.args = list(big.mark = ","), 
    digits = 1, col.names = c("Country", "Estimated Type", "Count (Last Week)", "Week Ending", "R - Lower CI", "R - Mean", "R - Uppper CI"))
Country Estimated Type Count (Last Week) Week Ending R - Lower CI R - Mean R - Uppper CI
Belgium deaths 52 2020-06-16 0.4 0.5 0.6
Canada deaths 340 2020-06-16 0.6 0.6 0.7
United_Kingdom deaths 1,139 2020-06-16 0.6 0.6 0.7
Germany deaths 89 2020-06-16 0.5 0.7 0.8
France deaths 227 2020-06-16 0.6 0.7 0.8
Sweden deaths 197 2020-06-16 0.7 0.8 0.9
Turkey deaths 114 2020-06-16 0.7 0.8 0.9
Italy deaths 407 2020-06-16 0.7 0.8 0.9
United_States_of_America deaths 5,120 2020-06-16 0.8 0.8 0.8
Brazil deaths 6,825 2020-06-16 0.9 0.9 1.0

11.1.3.2 Lowest \(R_{t,m}\) based on Cases

knitr::kable((table %>% filter(continent != "SA" & type == "cases" & count > 50) %>% arrange(Rt_mean))[1:10, c(-1, -2)], format.args = list(big.mark = ","), 
    digits = 1, col.names = c("Country", "Estimated Type", "Count (Last Week)", "Week Ending", "R - Lower CI", "R - Mean", "R - Uppper CI"))
Country Estimated Type Count (Last Week) Week Ending R - Lower CI R - Mean R - Uppper CI
Malaysia cases 165 2020-06-16 0.3 0.4 0.4
Djibouti cases 223 2020-06-16 0.3 0.4 0.4
Uganda cases 59 2020-06-16 0.4 0.5 0.6
Cuba cases 62 2020-06-16 0.4 0.6 0.7
Ireland cases 114 2020-06-16 0.5 0.6 0.7
Canada cases 2,903 2020-06-16 0.7 0.7 0.7
Tajikistan cases 488 2020-06-16 0.7 0.7 0.8
Nicaragua cases 346 2020-06-16 0.7 0.7 0.8
Finland cases 107 2020-06-16 0.6 0.7 0.9
Puerto_Rico cases 844 2020-06-16 0.7 0.8 0.8

11.1.3.3 Highest \(R_{t,m}\) based on Deaths

knitr::kable((table %>% filter(continent != "SA" & type == "deaths" & count > 50) %>% arrange(-Rt_mean))[1:10, c(-1, -2)], format.args = list(big.mark = ","), 
    digits = 1, col.names = c("Country", "Estimated Type", "Count (Last Week)", "Week Ending", "R - Lower CI", "R - Mean", "R - Uppper CI"))
Country Estimated Type Count (Last Week) Week Ending R - Lower CI R - Mean R - Uppper CI
Cameroon deaths 68 2020-06-16 1.9 2.5 3.1
Yemen deaths 96 2020-06-16 1.9 2.3 2.8
Iraq deaths 282 2020-06-16 1.6 1.8 2.0
Dominican_Republic deaths 66 2020-06-16 1.2 1.6 1.9
Egypt deaths 401 2020-06-16 1.3 1.5 1.6
Philippines deaths 87 2020-06-16 1.2 1.4 1.7
South_Africa deaths 488 2020-06-16 1.2 1.3 1.5
Romania deaths 93 2020-06-16 1.1 1.3 1.6
India deaths 2,434 2020-06-16 1.2 1.3 1.3
Colombia deaths 418 2020-06-16 1.1 1.2 1.3

11.1.3.4 Highest \(R_{t,m}\) based on Cases

knitr::kable((table %>% filter(continent != "SA" & type == "cases" & count > 50) %>% arrange(-Rt_mean))[1:10, c(-1, -2)], format.args = list(big.mark = ","), 
    digits = 1, col.names = c("Country", "Estimated Type", "Count (Last Week)", "Week Ending", "R - Lower CI", "R - Mean", "R - Uppper CI"))
Country Estimated Type Count (Last Week) Week Ending R - Lower CI R - Mean R - Uppper CI
Eritrea cases 68 2020-06-16 12.1 15.6 19.4
China cases 184 2020-06-16 2.6 3.1 3.5
Benin cases 178 2020-06-16 2.4 2.8 3.2
Kosovo cases 223 2020-06-16 1.8 2.0 2.2
Yemen cases 348 2020-06-16 1.8 2.0 2.2
Rwanda cases 161 2020-06-16 1.7 2.0 2.3
Albania cases 327 2020-06-16 1.8 2.0 2.2
Congo cases 200 2020-06-16 1.6 1.9 2.1
Mauritania cases 940 2020-06-16 1.7 1.8 1.9
Eswatini cases 166 2020-06-16 1.5 1.7 2.0

11.2 Result Plots

Let’s generate our plots for each country/province in a list. We filter out weeks where the upper end of confidence interval for \(R_{t,m}\) exceeds five.

country_plots <- list()
for (c in levels(Rt_data$country)) {
    p1 <- ggplot(Rt_ci_data %>% filter(country == c & ci == "95%" & Rt_ui <= 5), aes(x = date_mid, y = Rt_mean)) + geom_crossbar(position = position_dodge2(padding = 0), 
        aes(ymin = Rt_li, ymax = Rt_ui, colour = type, fill = type), width = tau) + scale_fill_manual(values = c(alpha("deepskyblue4", 0.45), 
        alpha("#8b0068", 0.45))) + scale_colour_manual(values = c(alpha("deepskyblue4"), alpha("#8b0068"))) + scale_y_continuous(limits = c(0, 
        5)) + scale_x_date(date_breaks = "1 months", labels = date_format("%e %b")) + theme_bw() + theme(axis.text.x = element_text(angle = 45, 
        hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + xlab("Week") + ylab(expression(R[t, m])) + ggtitle(c) + 
        geom_hline(yintercept = 1)
    country_plots[[c]] <- p1
}

11.2.1 South Africa

Below we plot estimates for all the provinces. Note that [1] recommends only to start using estimates after at least 12 cases have occurred. On that basis we do not have estimates for R based on deaths for a number of provinces.

ggarrange(country_plots[["EC"]], country_plots[["FS"]], country_plots[["GP"]], country_plots[["KZN"]], country_plots[["LP"]], country_plots[["MP"]], 
    country_plots[["NC"]], country_plots[["NW"]], country_plots[["WC"]], country_plots[["SA"]], ncol = 2, nrow = 5) + ggtitle("Reproductive number by province, with 95% confidence intervals")

11.2.2 Other Countries

Below we plot all the countries:

ggarrange(country_plots[["Brazil"]], country_plots[["Canada"]], country_plots[["Chile"]], country_plots[["India"]], country_plots[["Ireland"]], 
    country_plots[["Italy"]], country_plots[["Peru"]], country_plots[["South_Africa"]], country_plots[["Spain"]], country_plots[["United_Kingdom"]], 
    ncol = 2, nrow = 5) + ggtitle("Reproductive number by country, with 95% confidence intervals")

11.2.3 Scatter Plots of \(R_{t,m}^{cases}\) vs. \(R_{t,m}^{deaths}\)

Below we plot estimates of \(R_{t,m}\) based on cases versus those based on deaths for countries where these estimates range between 0.5 and 2.

11.2.3.1 South Africa

Below we plot where there were more than 5 deaths and cases in the last week. It appears that, generally, \(R_{t,m}^{cases}\) and \(R_{t,m}^{deaths}\) are consistent.

sp_data <- pivot_wider(table %>% filter(count >= 5 & continent == "SA"), id_cols = c("continent", "country"), names_from = "type", values_from = "Rt_mean")

ggplot(sp_data, aes(x = cases, y = deaths)) + geom_point(aes(label = country)) + geom_text(aes(label = country), hjust = 0, vjust = 0, size = 2) + 
    ggtitle("Reproductive number by country") + xlab(expression(R[t, m]^cases)) + ylab(expression(R[t, m]^deaths)) + scale_y_continuous(limits = c(0.5, 
    3)) + scale_x_continuous(limits = c(0.5, 3)) + theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + 
    guides(fill = guide_legend(ncol = 1)) + scale_color_hue(l = 50) + geom_abline(a = 0, b = 1)

11.2.3.2 Other countries

Below we plot where there were more than 25 deaths and cases in the last week. It appears that, generally, \(R_{t,m}^{cases}\) and \(R_{t,m}^{deaths}\) are consistent.

sp_data <- pivot_wider(table %>% filter(count >= 25 & continent != "SA"), id_cols = c("continent", "country"), names_from = "type", values_from = "Rt_mean")

ggplot(sp_data, aes(x = cases, y = deaths)) + geom_point(aes(color = continent, label = country)) + geom_text(aes(label = country), hjust = 0, 
    vjust = 0, size = 2) + ggtitle("Reproductive number by country") + xlab(expression(R[t, m]^cases)) + ylab(expression(R[t, m]^deaths)) + 
    scale_y_continuous(limits = c(0.5, 2)) + scale_x_continuous(limits = c(0.5, 2)) + theme_bw() + theme(axis.text.x = element_text(angle = 45, 
    hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + scale_color_hue(l = 50) + geom_abline(a = 0, b = 1)

11.3 Mobility

11.3.1 Simple plot

Below we plot weeks of estimates of \(R_{t,m}\) versus the average mobility index that was constructed. There is a clear link between mobility and the reproductive number. As it decreases \(R_{t,m}\) reduces:

ggplot(Rt_data %>% filter(type == "cases" & count > 100 & country_code %in% c(country_codes)), aes(x = average_mobility, y = Rt_mean)) + geom_point(aes(colour = country)) + 
    ggtitle("Reproductive number by mobility") + ylab(expression(R[t, m]^cases)) + # scale_y_continuous(limits = c(0, 10)) +
theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
    scale_color_hue(l = 50)

11.3.2 Modelling Results

TO DO

12 Discussion

From the basic plots it’s clear that South Africa and a few other countries appear to be on a different “slope” than European countries show. These include Brazil, Peru, Chile and India.

The above shows estimates for reproductive number using cases and deaths (\(R_{t,m}^{cases}\) and \(R_{t,m}^{deaths}\)) for each country \(m\) over time \(t\) in weeks.

From the current estimates it appears that, at present, in general South African provincial estimates based on cases and deaths correspond. An exception to this may be Eastern Cape with estimates based on deaths exceeding those based on cases.

It is also clear that estimates based on cases appear to precede the movements based on deaths over time. This can be expected as, all things being equal, infections and associated cases should be a precursor to deaths.

South Africa does not compare well, and appears to have one of the highest \(R_{t,m}^{deaths}\) in the world. This is higher than the \(R_{t,m}^{cases}\) which may be indicative of lower testing and/or backlogs with regard to testing.

On a scatter plot of countries most appear to have \(R_{t,m}^{cases}\) correlated well with \(R_{t,m}^{deaths}\). Chile and Afghanistan appear to be outliers with \(R_{t,m}^{deaths}\) higher than \(R_{t,m}^{cases}\) indicating perhaps limited testing, or alternatively epidemics that are slowing (given the leading nature of cases vs. deaths).

Overall it’s clear that having multiple measures of \(R_{t,m}\) appears useful and monitoring it’s value is something useful.

We also show that mobility is clearly correlated with \(R_{t,m}^{cases}\) in the countries shown.

Limitation of this method to estimate \(R_{t,m}\) are noted in [1]

  • It’s sensitive to changes in transmissibility, changes in contact patterns, depletion of the susceptible population and control measures.
  • It relies on an assumed serial interval assumptions.
  • The size of the time window can affect the volatility of results.
  • Results are time lagged with regards to true infection, more so in the case of the use of deaths.
  • It’s sensitive to changes in case (or death) detection.
  • The serial interval may change over time.

Despite these limitation we believe the ease of calculation of this method and the ability to use multiple sources makes it useful as a monitoring tool.

13 Detailed Output

Detailed output for all countries are saved to a comma-separated value file below. The file can be found here.

write.csv(Rt_data, "Rt_data.csv", row.names = FALSE)

14 Author

This report was prepared by Louis Rossouw. Please get in contact with Louis Rossouw if you have comments or wish to receive this regularly.

Louis Rossouw
Head of Research & Analytics
Gen Re | Life/Health Canada, South Africa, Australia, NZ, UK & Ireland
Email: LRossouw@GenRe.com Mobile: +27 71 355 2550

The views in this document represents that of the author and may not represent those of Gen Re. Also note that given the significant uncertainty involved with the parameters, data and methodology care should be taken with these numbers and any use of these numbers.

15 References

[1] A. Cori, N. M. Ferguson, C. Fraser, and S. Cauchemez, “A new framework and software to estimate time-varying reproduction numbers during epidemics,” American Journal of Epidemiology, vol. 178, no. 9, pp. 1505–1512, Sep. 2013, doi: 10.1093/aje/kwt133. [Online]. Available: https://doi.org/10.1093/aje/kwt133

[2] A. Cori, EpiEstim: EpiEstim: A package to estimate time varying reproduction numbers from epidemic curves. 2013 [Online]. Available: https://CRAN.R-project.org/package=EpiEstim

[3] V. Marivate et al., “Coronavirus disease (COVID-19) case data - South Africa.” Zenodo, 2020 [Online]. Available: https://zenodo.org/record/3888499

[4] European Centre for Disease Prevention and Control, “Data on the geographic distribution of COVID-19 cases worldwide.” European Union, 2020 [Online]. Available: https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide

[5] Google LLC, “Google COVID-19 community mobility reports.” 2020 [Online]. Available: https://www.google.com/covid19/mobility/

[6] P. Nouvellet et al., “Report 26: Reduction in mobility and COVID-19 transmission,” Imperial College London, 2020 [Online]. Available: https://www.imperial.ac.uk/mrc-global-infectious-disease-analysis/covid-19/report-26-mobility-transmission/

[7] N. Ferguson et al., “Report 9: Impact of non-pharmaceutical interventions (NPIs) to reduce COVID19 mortality and healthcare demand,” Imperial College London, 2020 [Online]. Available: https://www.imperial.ac.uk/mrc-global-infectious-disease-analysis/covid-19/report-9-impact-of-npis-on-covid-19/

LS0tDQp0aXRsZTogIkVzdGltYXRpbmcgdGhlIFJlcHJvZHVjdGl2ZSBOdW1iZXIgb2YgQ09WSUQtMTkiDQphdXRob3I6ICJMb3VpcyBSb3Nzb3V3Ig0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQ0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBnb29nbGVfYW5hbHl0aWNzX2hlYWRlci5odG1sDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICBpbmNsdWRlczoNCiAgICAgIGluX2hlYWRlcjogZ29vZ2xlX2FuYWx5dGljc19oZWFkZXIuaHRtbA0KY3NsOiBpZWVlX3dpdGhfdXJsLmNzbA0KYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYg0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Kcm0obGlzdD1scygpKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwgd2FybmluZyA9IEZBTFNFLCB0aWR5PVRSVUUsIGRwaT0zMDApDQpgYGANCg0KKioqVEhJUyBXT1JLIElTIFlFVCBUTyBCRSBSRVZJRVdFRCoqKg0KDQojIEludHJvZHVjdGlvbg0KDQpUaGlzIHBhcGVyIGNvbnRhaW5zIGVzdGltYXRlcyBmb3IgdGhlIHJlcHJvZHVjdGl2ZSBudW1iZXIgJFJfe3QsbX0kIG92ZXIgdGltZSAkdCQgaW4gdmFyaW91cyBjb3VudHJpZXMgJG0kLiAgSXQgYWxzbyBzaG93cyB0aGUgc2FtZSBmb3IgcHJvdmluY2VzIHdpdGhpbiBTb3V0aCBBZnJpY2EuLiAgVGhpcyBpcyBkb25lIHVzaW5nIHRoZSBtZXRob2RvbG9neSBhcyBkZXNjcmliZWQgaW4gW0BDb3JpMjAxM10uICBUaGVzZSBoYXZlIGJlZW4gaW1wbGVtZW50ZWQgaW4gUiB1c2luZyBgRXBpRXN0aW1gIHBhY2thZ2UgW0BDb3JpMjAxM2FdIHdoaWNoIGlzIHdoYXQgaXMgdXNlZCBoZXJlLiAgDQoNClRoaXMgcGFwZXIgYW5kIGl0J3MgcmVzdWx0cyBzaG91bGQgYmUgdXBkYXRlZCByb3VnaGx5IGRhaWx5IGFuZCBbaXMgYXZhaWxhYmxlIG9ubGluZV0oaHR0cHM6Ly9scm9zc291dy5naXRodWIuaW8vY292aWQtMTkvZXN0aW1hdGluZ19yLm5iLmh0bWw/dXRtX3NvdXJjZT1wYXBlciZ1dG1fbWVkaXVtPXBhcGVyX3VybCZ1dG1fY2FtcGFpZ249d2VlazIpLg0KDQojIFVwZGF0ZXMNCg0KQXMgdGhpcyBwYXBlciBpcyB1cGRhdGVkIG92ZXIgdGltZSB0aGlzIHNlY3Rpb24gd2lsbCBzdW1tYXJpc2Ugc2lnbmlmaWNhbnQgY2hhbmdlcy4gIFRoZSBjb2RlIHByb2R1Y2luZyB0aGlzIHBhcGVyIGlzIHRyYWNrZWQgdXNpbmcgR2l0LiBUaGUgR2l0IGNvbW1pdCBoYXNoIGZvciB0aGlzIHByb2plY3QgYXQgdGhlIHRpbWUgb2YgZ2VuZXJhdGluZyB0aGlzIHBhcGVyIHdhcyBgciBzeXN0ZW0oImdpdCByZXYtcGFyc2UgSEVBRCIsIGludGVybj1UUlVFKWAuDQoNCioqMjAyMC0wNi0xMioqDQoNCiogSW5pdGlhbCB2ZXJzaW9uLg0KDQoqKjIwMjAtMDYtMTMqKg0KDQoqIENvbWJpbmUgdGhlIHZlcnNpb24gZm9yIFNvdXRoIEFmcmljYSBhbmQgb3RoZXIgY291bnRyaWVzLg0KDQoqKjIwMjAtMDYtMTQqKg0KDQoqIEFkZCBHb29nbGUgTW9iaWxpdHkgZGF0YSBmb3IgY29tcGFyaXNvbiBhbmQgZnVydGhlciBtb2RlbGxpbmcuDQoNCioqMjAyMC0wNi0xNSoqDQoNCiogQWRkIG1vcmUgZGV0YWlscyBhYm91dCBtZXRob2RvbG9neS4NCg0KIyBMaWJyYXJpZXMNCg0KVGhlIHByb2plY3QgdXNlcyB0aGUgZm9sbG93aW5nIGxpYnJhcmllcy4NCg0KYGBge3IgbGlicmFyaWVzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KcmVxdWlyZShFcGlFc3RpbSkNCnJlcXVpcmUoRW52U3RhdHMpDQpyZXF1aXJlKGdncGxvdDIpDQpyZXF1aXJlKGdncHVicikNCnJlcXVpcmUobHVicmlkYXRlKQ0KcmVxdWlyZSh1dGlscykNCnJlcXVpcmUoaHR0cikNCnJlcXVpcmUoZHBseXIpDQpyZXF1aXJlKHRpZHlyKQ0KcmVxdWlyZShzY2FsZXMpDQpgYGANCg0KIyBDb3VudHJpZXMNCg0KQWxsIGNvdW50cmllcyBhdmFpbGFibGUgaW4gdGhlIGRhdGEgd2lsbCBiZSBhbmFseXNlZCBidXQgYWxzbyB0aGUgcHJvdmluY2VzIG9mIFNvdXRoIEFmcmljYS4gIFRoaXMgcGFwZXIgcHJvZHVjZXMgY2hhcnRzIGZvciB0aGUgZm9sbG93aW5nIGNvdW50cmllcyBvbmx5Og0KDQoqIEJyYXppbA0KKiBDYW5hZGENCiogQ2hpbGUNCiogSW5kaWENCiogSXJlbGFuZA0KKiBJdGFseQ0KKiBQZXJ1DQoqIFNvdXRoIEFmcmljYQ0KKiBTcGFpbg0KKiBVbml0ZWQgS2luZ2RvbQ0KDQpgYGB7ciBjb3VudHJpZXN9DQpjb3VudHJ5X2NvZGVzID0gYygiQlIiLCANCiAgICAgICAgICAgICAgIkNBIiwNCiAgICAgICAgICAgICAgIkNMIiwNCiAgICAgICAgICAgICAgIklOIiwNCiAgICAgICAgICAgICAgIklFIiwNCiAgICAgICAgICAgICAgIklUIiwNCiAgICAgICAgICAgICAgIlBFIiwNCiAgICAgICAgICAgICAgIlpBIiwNCiAgICAgICAgICAgICAgIkVTIiwNCiAgICAgICAgICAgICAgIlVLIg0KICAgICAgICAgICAgICApDQpgYGANCg0KIyBEYXRhDQoNCiMjIFNvdXRoIEFmcmljYQ0KDQojIyMgRG93bmxvYWQNCkRhdGEgaXMgZG93bmxvYWRlZCBmcm9tIHRoZSBHaXQgcmVwb3NpdG9yeSBhc3NvY2lhdGVkIHdpdGggW0BNYXJpdmF0ZTIwMjBdLg0KDQpgYGB7ciBkb3dubG9hZF9kYXRhX3phLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KIyBQcm92aW5jaWFsIERlYXRocw0KR0VUKA0KICB1cmwgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2RzZnNpL2NvdmlkMTl6YS9tYXN0ZXIvZGF0YS9jb3ZpZDE5emFfcHJvdmluY2lhbF9jdW11bGF0aXZlX3RpbWVsaW5lX2RlYXRocy5jc3YiLA0KICB3cml0ZV9kaXNrKA0KICAgICJjb3ZpZDE5emFfcHJvdmluY2lhbF9jdW11bGF0aXZlX3RpbWVsaW5lX2RlYXRocy5jc3YiLA0KICAgIG92ZXJ3cml0ZSA9IFRSVUUNCiAgKQ0KKQ0KDQojIFByb3ZpbmNpYWwgQ2FzZXMNCkdFVCgNCiAgdXJsID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9kc2ZzaS9jb3ZpZDE5emEvbWFzdGVyL2RhdGEvY292aWQxOXphX3Byb3ZpbmNpYWxfY3VtdWxhdGl2ZV90aW1lbGluZV9jb25maXJtZWQuY3N2IiwNCiAgd3JpdGVfZGlzaygNCiAgICAiY292aWQxOXphX3Byb3ZpbmNpYWxfY3VtdWxhdGl2ZV90aW1lbGluZV9jb25maXJtZWQuY3N2IiwNCiAgICBvdmVyd3JpdGUgPSBUUlVFDQogICkNCikNCmBgYA0KDQojIyMgRGF0YSBwcmVwZXJhdGlvbg0KDQpGaXJzdCByZWFkIGluIHRoZSBkYXRhIGZyb20gdGhlIGRvd25sb2FkZWQgY29tbWEtc2VwYXJhdGVkIHZhbHVlcyB0ZXh0IGZpbGUuDQoNCmBgYHtyIHJlYWRfZGF0YV96YX0NCiMgUmVhZCBmcm9tIENTVnMgYWJvdmUNCmRhdGFfemFfY2FzZXMgPC0NCiAgcmVhZC5jc3YoDQogICAgImNvdmlkMTl6YV9wcm92aW5jaWFsX2N1bXVsYXRpdmVfdGltZWxpbmVfY29uZmlybWVkLmNzdiIsDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCmRhdGFfemFfZGVhdGhzIDwtDQogIHJlYWQuY3N2KCJjb3ZpZDE5emFfcHJvdmluY2lhbF9jdW11bGF0aXZlX3RpbWVsaW5lX2RlYXRocy5jc3YiLA0KICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpgYGANCg0KSW4gdGhlIGNhc2UgZGF0YSBmaWxlIHJvdyAyMSBhbmQgMzIgY29udGFpbiBubyBwcm92aW5jaWFsIGRldGFpbHMuIFdlIGVzdGltYXRlZCBpdCBieSBzcHJlYWRpbmcgdGhlIG5hdGlvbmFsIHRvdGFsIHRvIHRoZSBwcm92aW5jZXMgaW4gcHJvcG9ydGlvbiB0byBhIG1peHR1cmUgb2YgdGhlIHByaW9yIGRheSBhbmQgdGhlIG5leHQgZGF5Lg0KDQpgYGB7ciBmaXhfemFfY2FzZXN9DQpkYXRhX3phX2Nhc2VzWzIxLCBjKCJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiLCAiVU5LTk9XTiIpXSA8LQ0KICBjb2xTdW1zKGRhdGFfemFfY2FzZXNbYygyMCwgMjIpLCBjKCJFQyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGUyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHUCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJLWk4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTFAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTVAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTlciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiV0MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVU5LTk9XTiIpXSkgLyBzdW0oZGF0YV96YV9jYXNlc1tjKDIwLCAyMiksIF0kdG90YWwpICogZGF0YV96YV9jYXNlc1syMSwgXSR0b3RhbA0KZGF0YV96YV9jYXNlc1szMiwgYygiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIiwgIlVOS05PV04iKV0gPC0NCiAgY29sU3VtcyhkYXRhX3phX2Nhc2VzW2MoMzEsIDMzKSwgYygiRUMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRlMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiR1AiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiS1pOIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkxQIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk1QIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5DIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk5XIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIldDIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlVOS05PV04iKV0pIC8gc3VtKGRhdGFfemFfY2FzZXNbYygzMSwgMzMpLCBdJHRvdGFsKSAqIGRhdGFfemFfY2FzZXNbMzIsIF0kdG90YWwNCg0KYGBgDQoNClRoZSBmb2xsb3dpbmcgZnVuY3Rpb24gd2lsbCBiZSBhcHBsaWVkIHRvIGNhc2UgYW5kIGRlYXRoIGRhdGEuICBJbiB0aGlzIGZ1bmN0aW9uIHRoZSBmb2xsb3dpbmcgb2NjdXJzOg0KDQoxLiBTY2FsZSB1cCB0aGUgcGVyIHByb3ZpbmNlIGRhdGEgZm9yIHVua25vd24gdmFsdWVzLg0KMi4gVGhpcyByZXN1bHRzIGluIHByb3ZpbmNpYWwgZGF0YSB3aGljaCBhcmUgbm90IHdob2xlIG51bWJlcnMuICBUaGVzZSBhcmUgcm91bmRlZCB0byB0aGUgbmVhcmVzdCB3aG9sZSBudW1iZXIuDQozLiBBIGBTQWAgY29sdW1uIGlzIGFkZGVkIGFzIHRoZSBzdW0gb2YgdGhlIG5ldyBwZXIgcHJvdmluY2UgZGF0YS4NCjQuIERhdGEgaXMgZm9ybWF0dGVkIGFuZCBkaXNhZ2dyZWdhdGVkIHN1Y2ggdGhhdCBpdGVtIHJlcHJlc2VudHMgdGhlIGluY3JlbWVudGFsIGNhc2VzIG9yIGRlYXRocyByYXRoZXIgdGhhbiBjdW11bGF0aXZlIGZpZ3VyZXMuDQo1LiBEYXRhIGlzIGZpbGxlZCB3aXRoIGRhdGEgKGFsYmVpdCB3aXRoIDAgY2FzZXMgb3IgZGVhdGhzKSBmb3IgYWxsIGRhdGVzIGluIHRoZSByYW5nZS4NCjYuIEFueSBpbmNyZW1lbnRhbCBjYXNlIG9yIGRlYXRoIGNvdW50cyB0aGF0IGFyZSBuZWdhdGl2ZSBhcmUgemVyb2Vpc2VkLg0KNi4gTmV3IGN1bXVsYXRpdmUgZmlndXJlcyBhcmUgY2FsY3VsYXRlZC4NCg0KDQpgYGB7ciBmaXhfZGF0YV96YX0NCmZpeF9kYXRhX3phIDwtDQogIGZ1bmN0aW9uKGRhdGFfemEsDQogICAgICAgICAgIHN0YXJ0X2RhdGUgPSBhcy5EYXRlKCIyMDIwLTAzLTAxIiksDQogICAgICAgICAgIGVuZF9kYXRlID0gYXMuRGF0ZSgiMjAyMC0wMy0zMSIpKSB7DQogICAgIyBTY2FsZSBwcm92aW5jZXMgYnkgc2NhbGUgZmFjdG9yIChhc3N1bWUgdW5rbm93biBhcmUgaW4gcHJvcG9ydGlvbikNCiAgICBkYXRhX3phWywgYygiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIildIDwtDQogICAgICBkYXRhX3phWywgYygiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIildICoNCiAgICAgICgxICsgZGF0YV96YSRVTktOT1dOIC8NCiAgICAgICAgIHJvd1N1bXMoZGF0YV96YVssIGMoIkVDIiwgIkZTIiwgIkdQIiwgIktaTiIsICJMUCIsICJNUCIsICJOQyIsICJOVyIsICJXQyIpXSkpDQogICAgDQogICAgIyBPbmx5IHNlbGVjdCBjb2x1bW5zIHdlIG5lZWQNCiAgICBkYXRhX3phIDwtIGRhdGFfemEgJT4lDQogICAgICBzZWxlY3QoImRhdGUiLCAiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIikNCiAgICBkYXRhX3phJGRhdGUgPC0gYXMuRGF0ZShkYXRhX3phJGRhdGUsICIlZC0lbS0lWSIpDQogICAgDQogICAgIyBSb3VuZCBkYXRhIHNvIHdlIGhhdmUgaW50ZWdlciBjYXNlcw0KICAgIGRhdGFfemFbLCBjKCJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiKV0gPC0NCiAgICAgIHJvdW5kKGRhdGFfemFbLCBjKCJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiKV0gLCAwKQ0KICAgIA0KICAgICMgQ2FsY3VsYXRlIGEgbmV3IFNBIGNvbHVtbg0KICAgIGRhdGFfemEkU0EgPC0NCiAgICAgIHJvd1N1bXMoZGF0YV96YVssIGMoIkVDIiwgIkZTIiwgIkdQIiwgIktaTiIsICJMUCIsICJNUCIsICJOQyIsICJOVyIsICJXQyIpXSkNCiAgICANCiAgICAjICJNZWx0IiB0aGUgZGF0YQ0KICAgIGRhdGFfemEgPC0NCiAgICAgIHBpdm90X2xvbmdlcigNCiAgICAgICAgZGF0YV96YSwNCiAgICAgICAgY29scyA9IGMoIkVDIiwgIkZTIiwgIkdQIiwgIktaTiIsICJMUCIsICJNUCIsICJOQyIsICJOVyIsICJXQyIsICJTQSIpLA0KICAgICAgICBuYW1lc190byA9ICJwcm92aW5jZSIsDQogICAgICAgIHZhbHVlc190byA9ICJjb3VudCINCiAgICAgICkNCiAgICANCiAgICAjIEdldHRpbmcgZGFpbHkgZGF0YSBmcm9tIHRoZSBjdW11bGF0aXZlIGRhdGEgc2V0DQogICAgZGF0YV96YSA9ICBkYXRhX3phICU+JQ0KICAgICAgZ3JvdXBfYnkocHJvdmluY2UpICU+JQ0KICAgICAgYXJyYW5nZShkYXRlKSAlPiUNCiAgICAgIG11dGF0ZShjb3VudCA9IGNvdW50IC0gbGFnKGNvdW50LCBkZWZhdWx0ID0gMCkpICU+JQ0KICAgICAgdW5ncm91cCgpDQoNCiAgICAjIGFkZCBtaXNzaW5nIGRhdGVzDQogICAgYWxsX2RhdGVzIDwtIGV4cGFuZF9ncmlkKGRhdGUgPSBzZXEoc3RhcnRfZGF0ZSwgZW5kX2RhdGUsIDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm92aW5jZSA9IGxldmVscyhhcy5mYWN0b3IoZGF0YV96YSRwcm92aW5jZSkpKQ0KICAgIA0KICAgICMgam9pbg0KICAgIGRhdGFfemEgPC0gbGVmdF9qb2luKGFsbF9kYXRlcywgZGF0YV96YSwgYnkgPWMoImRhdGUiLCJwcm92aW5jZSIpKQ0KICAgIA0KICAgICNwcm92aW5jZSBmYWN0b3INCiAgICBkYXRhX3phJHByb3ZpbmNlIDwtIGFzLmZhY3RvcihkYXRhX3phJHByb3ZpbmNlKQ0KDQogICAgIyAwIGZvciBOQXMNCiAgICBkYXRhX3phJGNvdW50IDwtIGlmZWxzZShpcy5uYShkYXRhX3phJGNvdW50KSwgMCwgZGF0YV96YSRjb3VudCkNCiAgICANCiAgICAjIHJlbW92ZSBuZWdhdGl2ZXMNCiAgICBkYXRhX3phJGNvdW50IDwtIGlmZWxzZShkYXRhX3phJGNvdW50IDwgMCwgMCwgZGF0YV96YSRjb3VudCkNCiAgICANCiAgICAjIGdldCBjdW11bGF0aXZlIGNvdW50cw0KICAgIGRhdGFfemEgPC0gZGF0YV96YSAlPiUNCiAgICAgIGdyb3VwX2J5KHByb3ZpbmNlKSAlPiUNCiAgICAgIG11dGF0ZShjdW11bGF0aXZlX2NvdW50ID0gY3Vtc3VtKGNvdW50KSkgJT4lDQogICAgICB1bmdyb3VwKCkNCiAgICANCiAgICByZXR1cm4oZGF0YV96YSkNCiAgfQ0KYGBgDQoNCkJlbG93IHdlIHVzZSB0aGUgZnVuY3Rpb24gYWJvdmUgdG8gcHJvY2VzcyBkZWF0aHMgYW5kIGNhc2VzIGFuZCB0aGVuIGNvbWJpbmUgdGhlbSBpbnRvIGEgc2luZ2xlIGRhdGFzZXQuDQoNCmBgYHtyIGFwcGx5X2ZpeF9kYXRhX3phfQ0Kc3RhcnRfZGF0ZSA8LSBtaW4oYXMuRGF0ZShkYXRhX3phX2Nhc2VzJGRhdGUsICIlZC0lbS0lWSIpKQ0KZW5kX2RhdGUgPC0gbWF4KGFzLkRhdGUoZGF0YV96YV9jYXNlcyRkYXRlLCAiJWQtJW0tJVkiKSkNCmRhdGFfemFfY2FzZXMgPC0NCiAgZml4X2RhdGFfemEoZGF0YV96YV9jYXNlcywgc3RhcnRfZGF0ZSA9IHN0YXJ0X2RhdGUsIGVuZF9kYXRlID0gZW5kX2RhdGUpDQpkYXRhX3phX2RlYXRocyA8LQ0KICBmaXhfZGF0YV96YShkYXRhX3phX2RlYXRocywgc3RhcnRfZGF0ZSA9IHN0YXJ0X2RhdGUsIGVuZF9kYXRlID0gZW5kX2RhdGUpDQoNCmRhdGFfemFfY2FzZXM8LWNiaW5kKCJjYXNlcyIsZGF0YV96YV9jYXNlcykNCmRhdGFfemFfZGVhdGhzPC1jYmluZCgiZGVhdGhzIixkYXRhX3phX2RlYXRocykNCmNvbG5hbWVzKGRhdGFfemFfY2FzZXMpWzFdPC0idHlwZSINCmNvbG5hbWVzKGRhdGFfemFfZGVhdGhzKVsxXTwtInR5cGUiDQoNCiMgY29tYmluZWQNCmRhdGFfemEgPC0NCiAgcmJpbmQoZGF0YV96YV9jYXNlcyxkYXRhX3phX2RlYXRocykNCg0KIyByZW1vdmUgZGF0YSBzZXRzIG5vIGxvbmdlciBuZWVkZWQNCnJtKCJkYXRhX3phX2Nhc2VzIiwgImRhdGFfemFfZGVhdGhzIiwgInN0YXJ0X2RhdGUiLCAiZW5kX2RhdGUiLCAiZml4X2RhdGFfemEiKQ0KYGBgDQoNCg0KIyMgT3RoZXIgY291bnRyaWVzDQoNCiMjIyBEb3dubG9hZA0KDQpEYXRhIGZvciBvdGhlciBjb3VudHJpZXMgYXJlIGRvd25sb2FkZWQgZnJvbSBbQEVDREMyMDIwXS4NCg0KYGBge3IgZG93bmxvYWRfZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCkdFVCgNCiAgdXJsID0gImh0dHBzOi8vb3BlbmRhdGEuZWNkYy5ldXJvcGEuZXUvY292aWQxOS9jYXNlZGlzdHJpYnV0aW9uL2NzdiIsDQogIHdyaXRlX2Rpc2soDQogICAgImVjZGNfY2FzZWRpc3RyaWJ1dGlvbi5jc3YiLA0KICAgIG92ZXJ3cml0ZSA9IFRSVUUNCiAgKQ0KKQ0KYGBgDQoNCiMjIyBEYXRhIHByZXBlcmF0aW9uDQoNCkZpcnN0IHJlYWQgaW4gdGhlIGRhdGEgZnJvbSB0aGUgZG93bmxvYWRlZCBjb21tYS1zZXBhcmF0ZWQgdmFsdWVzIHRleHQgZmlsZS4NCg0KYGBge3IgcmVhZF9kYXRhfQ0KIyBSZWFkIGZyb20gQ1NWcyBhYm92ZQ0KZGF0YSA8LQ0KICByZWFkLmNzdigNCiAgICAiZWNkY19jYXNlZGlzdHJpYnV0aW9uLmNzdiIsDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCmBgYA0KDQpVcGRhdGUgZGF0YSBieSBkb2luZyB0aGUgZm9sbG93aW5nOg0KDQoxLiBGb3JtYXQgZGF0ZXMuDQoyLiBTZXQgY29sdW1uIG5hbWVzLg0KMy4gQWRkIGRhdGEgd2hlcmUgZGF0ZXMgd2VyZSBza2lwcGVkICh3aXRoIDAgY2FzZXMgb3IgZGVhdGhzKQ0KNC4gIk1lbHQiIHRoZSBkYXRhIGRvd24uDQo1LiBTZXQgZmllbGRzIGFzIGZhY3RvcnMgYXMgbmVlZGVkLg0KNi4gR2V0IGN1bXVsYXRpdmUgbnVtYmVycy4NCg0KYGBge3IgdXBkYXRlX2RhdGF9DQojIGdldCBkYXRlDQpkYXRhJGRhdGUgPC0gYXMuRGF0ZShkYXRhJGRhdGVSZXAsIGZvcm1hdCA9ICIlZC8lbS8lWSIpDQoNCiMgZ2V0IGNhc2VzIGFuZCBkZWF0aHMNCmRhdGEgPC0NCiAgZGF0YSAlPiUgc2VsZWN0KGNvbnRpbmVudEV4cCwgZGF0ZSwgZ2VvSWQsIGNvdW50cmllc0FuZFRlcnJpdG9yaWVzLCBjYXNlcywgZGVhdGhzKQ0KDQojIGNvbHVtbiBuYW1lcw0KY29sbmFtZXMoZGF0YSkgPC0NCiAgYygiY29udGluZW50IiwgImRhdGUiLCAiY291bnRyeV9jb2RlIiwgImNvdW50cnkiLCAiY2FzZXMiLCAiZGVhdGhzIikNCg0KIyBhZGQgMCBkYXRhIGZvciBkYXRlcyB0aGF0IHdlcmUgc2tpcHBlZA0KZm9yIChjIGluIGxldmVscyhhcy5mYWN0b3IoZGF0YSRjb3VudHJ5KSkpIHsNCiAgY19kYXRlcyA8LSAoZGF0YSAlPiUgZmlsdGVyKGNvdW50cnkgPT0gYykpJGRhdGUNCiAgY2hlY2tfZGF0ZXMgPC0gc2VxKG1pbihjX2RhdGVzKSwgbWF4KGNfZGF0ZXMpLCBieSA9IDEpDQogIG1pc3NpbmdfZGF0ZXMgPC0gY2hlY2tfZGF0ZXNbIWNoZWNrX2RhdGVzICVpbiUgY19kYXRlc10NCiAgY29udGluZW50IDwtIChkYXRhICU+JSBmaWx0ZXIoY291bnRyeSA9PSBjKSkkY29udGluZW50WzFdDQogIGNvdW50cnlfY29kZSA8LSAoZGF0YSAlPiUgZmlsdGVyKGNvdW50cnkgPT0gYykpJGNvdW50cnlfY29kZVsxXQ0KICBpZiAobGVuZ3RoKG1pc3NpbmdfZGF0ZXMpID4gMCkgew0KICAgIG1pc3NpbmdfZGF0YSA8LSBkYXRhLmZyYW1lKA0KICAgICAgY29udGluZW50ID0gY29udGluZW50LA0KICAgICAgZGF0ZSA9IG1pc3NpbmdfZGF0ZXMsDQogICAgICBjb3VudHJ5X2NvZGUgPSBjb3VudHJ5X2NvZGUsDQogICAgICBjb3VudHJ5ID0gcmVwKGMsIGxlbmd0aChtaXNzaW5nX2RhdGVzKSksDQogICAgICBjYXNlcyA9IHJlcCgwLCBsZW5ndGgobWlzc2luZ19kYXRlcykpLA0KICAgICAgZGVhdGhzID0gcmVwKDAsIGxlbmd0aChtaXNzaW5nX2RhdGVzKSkNCiAgICApDQogICAgZGF0YSA8LSByYmluZChkYXRhLCBtaXNzaW5nX2RhdGEpDQogIH0NCn0NCg0KIyAiTWVsdCIgdGhlIGRhdGENCmRhdGEgPC0NCiAgcGl2b3RfbG9uZ2VyKA0KICAgIGRhdGEsDQogICAgY29scyA9IGMoImNhc2VzIiwgImRlYXRocyIpLA0KICAgIG5hbWVzX3RvID0gInR5cGUiLA0KICAgIHZhbHVlc190byA9ICJjb3VudCINCiAgKQ0KDQojIGZpZWxkcyBhcyBmYWN0b3JzDQpkYXRhJGNvbnRpbmVudCA8LSBhcy5mYWN0b3IoZGF0YSRjb250aW5lbnQpDQpkYXRhJGNvdW50cnlfY29kZSA8LSBhcy5mYWN0b3IoZGF0YSRjb3VudHJ5X2NvZGUpDQpkYXRhJGNvdW50cnkgPC0gYXMuZmFjdG9yKGRhdGEkY291bnRyeSkNCmRhdGEkdHlwZSA8LSBhcy5mYWN0b3IoZGF0YSR0eXBlKQ0KDQojIG1ha2Ugc3VyZSBpdCBpcyBhIGRhdGEgZnJhbWUNCmRhdGEgPC0gYXMuZGF0YS5mcmFtZShkYXRhKQ0KDQojIHplcm9pc2UgY291bnRzIGJlbG93IDANCmRhdGEkY291bnQgPC0gIGlmZWxzZShkYXRhJGNvdW50PDAsIDAgLCBkYXRhJGNvdW50KQ0KDQojb3JkZXIgYW5kIGdldCBncm91cCBieQ0KZGF0YSA8LSAgZGF0YSAlPiUNCiAgZ3JvdXBfYnkoY291bnRyeV9jb2RlLCBjb3VudHJ5LCB0eXBlKSAlPiUNCiAgYXJyYW5nZShjb3VudHJ5X2NvZGUsIGNvdW50cnksIHR5cGUsIGRhdGUpICU+JQ0KICBtdXRhdGUoY3VtdWxhdGl2ZV9jb3VudCA9IGN1bXN1bShjb3VudCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KYGBgDQoNCiMjIEdvb2dsZSBNb2JpbGl0eSBEYXRhDQoNCkdvb2dsZSByZWxlYXNlZCBtb2JpbGl0eSBkYXRhIGluZGV4ZXMgdGhhdCBkZXZpYXRpb25zIGZyb20gYSBiYXNlbGluZSBvZiBtb3ZlbWVudCBkdXJpbmcgMyBKYW51YXJ5IHRvIDYgRmVicnVhcnkgMjAyMCBbQEdvb2dsZTIwMjBdLiAgVGhpcyBkYXRhIGlzIGRvd25sb2FkZWQgYW5kIHByZXBhcmVkIGZvciBsaW5raW5nIHRvIG91ciBlc3RpbWF0ZXMgb2YgJFJfe3QsbX0kLg0KDQojIyMgRG93bmxvYWQNCg0KVGhlIGRhdGEgaXMgbWFkZSBhdmFpbGFibGUgYXMgYSBjb21tYS1zZXBhcmF0ZWQgZmlsZSB3aGljaCBpcyB1cGRhdGVkIHJlZ3VsYXJseSBidXQgbm90IGRhaWx5LiAgRGF0YSBpcyBzb21ldGltZXMgdXAgdG8gYSB3ZWVrIGJlaGluZC4NCg0KYGBge3IgZG93bmxvYWRfZ29vZ2xlX21vYmlsaXR5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KR0VUKA0KICB1cmwgPSAiaHR0cHM6Ly93d3cuZ3N0YXRpYy5jb20vY292aWQxOS9tb2JpbGl0eS9HbG9iYWxfTW9iaWxpdHlfUmVwb3J0LmNzdiIsDQogIHdyaXRlX2Rpc2soDQogICAgImdvb2dsZV9tb2JpbGl0eV9kYXRhLmNzdiIsDQogICAgb3ZlcndyaXRlID0gVFJVRQ0KICApDQopDQpgYGANCg0KIyMjIFByZXBhcmluZyB0aGUgbW9iaWxpdHkgZGF0YQ0KDQpGaXJzdCByZWFkIGluIHRoZSBmaWxlOg0KDQpgYGB7ciByZWFkX2dvb2dsZV9tb2JpbGl0eX0NCiMgUmVhZCBmcm9tIENTVnMgYWJvdmUNCmdvb2dsZV9tb2JpbGl0eV9kYXRhIDwtDQogIHJlYWQuY3N2KA0KICAgICJnb29nbGVfbW9iaWxpdHlfZGF0YS5jc3YiLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwNCiAgICBuYS5zdHJpbmdzID0gIiINCiAgKQ0KYGBgDQoNCkJlbG93IHRoZSBkYXRhIGlzIHByZXBhcmVkIGJ5IGRvaW5nIHRoZSBmb2xsb3dpbmc6DQoNCjEuIEZpeGluZyBkYXRlcw0KMi4gUmV0YWluaW5nIG9ubHkgY291bnRyeSBsZXZlbCBtb2JpbGl0eSBkYXRhLCBleGNlcHQgZm9yIFNvdXRoIEFmcmljYSB3aGVyZSBjb3VudHJ5IGFuZCBwcm92aW5jaWFsIGRhdGEgaXMgcmV0YWluZWQuDQozLiBSZW5hbWluZyB0aGUgZmllbGRzIGNvbnNpc3RlbnRseS4NCjQuIFZhcmlvdXMgdHJhbnNmb3JtYXRpb25zIHRvIG1ha2Ugam9pbmluZyB0byBvdGhlciBkYXRhIGVhc2llciBsYXRlci4NCg0KYGBge3IgcHJlcF9nb29nbGVfbW9iaWxpdHl9DQojIGZpeCBkYXRlcw0KZ29vZ2xlX21vYmlsaXR5X2RhdGEkZGF0ZSA8LQ0KICBhcy5EYXRlKGdvb2dsZV9tb2JpbGl0eV9kYXRhJGRhdGUsIGZvcm1hdCA9ICIlWS0lbS0lZCIpDQoNCiMga2VlcCBvbmx5IGNvdW50cnkgbGV2ZWwgZGF0YSBidXQga2VlcCBhbGwgZGF0YSBmb3IgWkENCmdvb2dsZV9tb2JpbGl0eV9kYXRhIDwtIGdvb2dsZV9tb2JpbGl0eV9kYXRhICU+JQ0KICBmaWx0ZXIoY291bnRyeV9yZWdpb25fY29kZSA9PSAiWkEiIHwgaXMubmEoc3ViX3JlZ2lvbl8xKSkNCg0KIyBwaWNrIG9ubHkgdGhlIGZpZWxkcyByZXF1aXJlZA0KZ29vZ2xlX21vYmlsaXR5X2RhdGEgPC0gZ29vZ2xlX21vYmlsaXR5X2RhdGEgJT4lDQogIHNlbGVjdCgNCiAgICBjb3VudHJ5X3JlZ2lvbl9jb2RlLA0KICAgIGNvdW50cnlfcmVnaW9uLA0KICAgIHN1Yl9yZWdpb25fMSwNCiAgICBpc29fMzE2Nl8yX2NvZGUsDQogICAgZGF0ZSwNCiAgICByZXRhaWxfYW5kX3JlY3JlYXRpb25fcGVyY2VudF9jaGFuZ2VfZnJvbV9iYXNlbGluZSwNCiAgICBncm9jZXJ5X2FuZF9waGFybWFjeV9wZXJjZW50X2NoYW5nZV9mcm9tX2Jhc2VsaW5lLA0KICAgIHBhcmtzX3BlcmNlbnRfY2hhbmdlX2Zyb21fYmFzZWxpbmUsDQogICAgdHJhbnNpdF9zdGF0aW9uc19wZXJjZW50X2NoYW5nZV9mcm9tX2Jhc2VsaW5lLA0KICAgIHdvcmtwbGFjZXNfcGVyY2VudF9jaGFuZ2VfZnJvbV9iYXNlbGluZSwNCiAgICByZXNpZGVudGlhbF9wZXJjZW50X2NoYW5nZV9mcm9tX2Jhc2VsaW5lDQogICkNCg0KIyByZW5hbWUgdGhlIGZpZWxkcw0KY29sbmFtZXMoZ29vZ2xlX21vYmlsaXR5X2RhdGEpIDwtIGMoDQogICJjb3VudHJ5X2NvZGUiLA0KICAiY291bnRyeSIsDQogICJwcm92aW5jZSIsDQogICJwcm92aW5jZV9jb2RlIiwNCiAgImRhdGUiLA0KICAicmV0YWlsX2FuZF9yZWNyZWF0aW9uIiwNCiAgImdyb2NlcnlfYW5kX3BoYXJtYWN5IiwNCiAgInBhcmtzIiwNCiAgInRyYW5zaXRfc3RhdGlvbnMiLA0KICAid29ya3BsYWNlcyIsDQogICJyZXNpZGVudGlhbCINCikNCg0KIyBtb3ZlIHByb3ZpbmNlIGRhdGEgdG8gY291bnRyeSBmb3IgZWFzZSBsYXRlcg0KZ29vZ2xlX21vYmlsaXR5X2RhdGEkY291bnRyeV9jb2RlWyFpcy5uYShnb29nbGVfbW9iaWxpdHlfZGF0YSRwcm92aW5jZV9jb2RlKV0gPC0NCiAgZ29vZ2xlX21vYmlsaXR5X2RhdGEkcHJvdmluY2VfY29kZVshaXMubmEoZ29vZ2xlX21vYmlsaXR5X2RhdGEkcHJvdmluY2VfY29kZSldDQpnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5WyFpcy5uYShnb29nbGVfbW9iaWxpdHlfZGF0YSRwcm92aW5jZV9jb2RlKV0gPC0NCiAgZ29vZ2xlX21vYmlsaXR5X2RhdGEkcHJvdmluY2VbIWlzLm5hKGdvb2dsZV9tb2JpbGl0eV9kYXRhJHByb3ZpbmNlX2NvZGUpXQ0KDQojIHJlbmFtZSBLWk4gY29uc2lzdGVudGx5DQpnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5X2NvZGVbZ29vZ2xlX21vYmlsaXR5X2RhdGEkY291bnRyeV9jb2RlID09ICJaQS1OTCJdIDwtDQogICJaQS1LWk4iDQoNCiMgZHJvcCBwcm92aW5jZSBjb2x1bW5zDQpnb29nbGVfbW9iaWxpdHlfZGF0YSA8LSBnb29nbGVfbW9iaWxpdHlfZGF0YVssIGMoLTMsLTQpXQ0KDQojIGRpdmlkZSBpbmRleGVzIGJ5IDEwMCB0byBoYXZlIG51bWJlcnMgYmV0d2VlbiAwIGFuZCAxDQpnb29nbGVfbW9iaWxpdHlfZGF0YVssIDQ6OV0gPC0gZ29vZ2xlX21vYmlsaXR5X2RhdGFbLCA0OjldIC8gMTAwDQoNCiMgZml4IEdCIGNvZGUgdG8gVUsNCmdvb2dsZV9tb2JpbGl0eV9kYXRhJGNvdW50cnlfY29kZVtnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5X2NvZGUgPT0gIkdCIl0gPC0NCiAgIlVLIg0KDQojIG1ha2UgZmFjdG9ycw0KZ29vZ2xlX21vYmlsaXR5X2RhdGEkY291bnRyeV9jb2RlIDwtDQogIGFzLmZhY3Rvcihnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5X2NvZGUpDQpnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5IDwtDQogIGFzLmZhY3Rvcihnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5KQ0KYGBgDQoNCkZ1cnRoZXIgdG8gdGhlIGFib3ZlIHdlIGFsc28gY2FsY3VsYXRlIGFuIGF2ZXJhZ2UgbW9iaWxpdHkgaW5kZXggcGVyIFtATm91dmVsbGV0MjAyMF0uICBUaGlzIGluZGV4IGF2ZXJhZ2VzIHRoZSBtb2JpbGl0eSBpbmRleGVzIGJ1dCBleGNsdWRlIHRoZSBmb2xsb3dpbmcgaW5kZXhlczoNCg0KKiBSZXNpZGVudGlhbCBpbmRleCBpcyBleGNsdWRlZCBhcyBpdCdzIGxlc3MgbGlrZWx5IHRvIGRyaXZlIHRoZSBlcGlkZW1pYyAoYW5kIGlzIGludmVyc2VseSBjb3JyZWxhdGVkKSwNCiogTW9iaWxpdHkgaW4gcGFya3MgaXMgYWxzbyB1bmxpa2VseSB0byBiZSBhIGRyaXZpbmcgZmFjdG9yLg0KDQpgYGB7ciBjYWxjX2F2ZXJhZ2VfbW9iaWxpdHl9DQpnb29nbGVfbW9iaWxpdHlfZGF0YSRhdmVyYWdlX21vYmlsaXR5IDwtICgNCiAgZ29vZ2xlX21vYmlsaXR5X2RhdGEkcmV0YWlsX2FuZF9yZWNyZWF0aW9uICsNCiAgICBnb29nbGVfbW9iaWxpdHlfZGF0YSRncm9jZXJ5X2FuZF9waGFybWFjeSArDQogICAgZ29vZ2xlX21vYmlsaXR5X2RhdGEkdHJhbnNpdF9zdGF0aW9ucyArDQogICAgZ29vZ2xlX21vYmlsaXR5X2RhdGEkd29ya3BsYWNlcw0KKSAvIDQNCmBgYA0KDQojIyBDb21iaW5lIFNvdXRoIEFmcmljYW4gZGF0YSB3aXRoIG90aGVyIGRhdGENCg0KVG8gc2ltcGxpZnkgZnVydGhlciBhbmFseXNpcyBpdCdzIGVhc2llciB0byBjb21iaW5lIGFsbCBkYXRhIGluIGEgc2luZ2xlIGRhdGFzZXQ6DQoNCmBgYHtyIGNvbWJpbmVfZGF0YX0NCiMgQWRkIGEgImNvbnRpbmVudCIgZmllbGQgdG8gZGF0YV96YSBhbmQgY29tYmluZQ0KZGF0YV96YSA8LSBjYmluZCgiU0EiLCBkYXRhX3phKQ0KY29sbmFtZXMoZGF0YV96YSlbMV0gPC0gImNvbnRpbmVudCINCmNvbG5hbWVzKGRhdGFfemEpWzRdIDwtICJjb3VudHJ5Ig0KZGF0YV96YSRjb3VudHJ5X2NvZGUgPC0gcGFzdGUwKCJaQS0iLCBkYXRhX3phJGNvdW50cnkpDQpkYXRhIDwtIHJiaW5kKGRhdGEsIGRhdGFfemEpDQoNCiMgcmVtb3ZlIGFsbCBvYmplY3RzIGV4Y2VwdCB3aGF0IHdlIG5lZWQNCnJtKGxpc3QgPSBscygpWyFscygpICVpbiUgYygiZGF0YSIsICJjb3VudHJ5X2NvZGVzIiwgImdvb2dsZV9tb2JpbGl0eV9kYXRhIildKQ0KYGBgDQoNCiMgQmFzaWMgRXhwbG9yYXRpb24NCg0KIyMgU291dGggQWZyaWNhDQoNCkJlbG93IHdlIHBsb3QgY3VtdWxhdGl2ZSBjYXNlIGNvdW50IG9uIGEgbG9nIHNjYWxlIGJ5IHByb3ZpbmNlOg0KDQpgYGB7ciBwbG90X2Nhc2VzX3phfQ0KZ2dwbG90KA0KICBkYXRhICU+JSBmaWx0ZXIoY29udGluZW50ID09ICJTQSIgJg0KICAgICAgICAgICAgICAgICAgICB0eXBlID09ICJjYXNlcyIgJiBjdW11bGF0aXZlX2NvdW50ID4gMCksDQogIGFlcyh4ID0gZGF0ZSwgeSA9IGN1bXVsYXRpdmVfY291bnQpDQopICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IGNvdW50cnkpLCBzaXplID0gMSkgKw0KICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IGNvbW1hKSArDQogIGdndGl0bGUoIkN1bXVsYXRpdmUgQ2FzZXMgYnkgUHJvdmluY2UiKSArDQogIHhsYWIoIkRhdGUiKSArDQogIHlsYWIoIkN1bXVsYXRpdmUgQ2FzZXMiKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAxKSkgKw0KICBzY2FsZV9jb2xvcl9odWUobCA9IDUwKQ0KYGBgDQoNCkJlbG93IHdlIHBsb3QgdGhlIGN1bXVsYXRpdmUgZGVhdGhzIGJ5IHByb3ZpbmNlIG9uIGEgbG9nIHNjYWxlOg0KDQpgYGB7ciBwbG90X2RlYXRoc196YX0NCmdncGxvdCgNCiAgZGF0YSAlPiUgZmlsdGVyKGNvbnRpbmVudCA9PSAiU0EiICYNCiAgICAgICAgICAgICAgICAgICAgdHlwZSA9PSAiZGVhdGhzIiAmIGN1bXVsYXRpdmVfY291bnQgPiAwKSwNCiAgYWVzKHggPSBkYXRlLCB5ID0gY3VtdWxhdGl2ZV9jb3VudCkNCikgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gY291bnRyeSksIHNpemUgPSAxKSArDQogIHNjYWxlX3lfbG9nMTAobGFiZWxzID0gY29tbWEpICsNCiAgZ2d0aXRsZSgiQ3VtdWxhdGl2ZSBEZWF0aHMgYnkgUHJvdmluY2UiKSArDQogIHhsYWIoIkRhdGUiKSArDQogIHlsYWIoIkN1bXVsYXRpdmUgRGVhdGhzIikgKw0KICB0aGVtZV9idygpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKw0KICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMSkpICsNCiAgc2NhbGVfY29sb3JfaHVlKGwgPSA1MCkNCmBgYA0KDQpCZWxvdyB3ZSBwbG90IGF2ZXJhZ2UgbW9iaWxpdHkgaW5kZXhlcyBieSBwcm92aW5jZToNCg0KYGBge3IgcGxvdF9tb2JpbGl0eV9wcm92aW5jZX0NCmdncGxvdChkYXRhID0gZ29vZ2xlX21vYmlsaXR5X2RhdGFbc3Vic3RyKGFzLmNoYXJhY3Rlcihnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5X2NvZGUpLCAxLCAyKSA9PQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJaQSIsIF0sDQogICAgICAgYWVzKHggPSBkYXRlLCB5ID0gYXZlcmFnZV9tb2JpbGl0eSwgY29sb3VyID0gY291bnRyeSkpICsNCiAgZ2VvbV9saW5lKCkgKyBzY2FsZV9jb2xvcl9odWUobCA9IDUwKSArDQogIHRoZW1lX2J3KCkNCmBgYA0KDQojIyBPdGhlciBDb3VudHJpZXMNCg0KQmVsb3cgd2UgcGxvdCBjdW11bGF0aXZlIGNhc2UgY291bnQgb24gYSBsb2cgc2NhbGUgYnkgY291bnRyeToNCg0KYGBge3IgcGxvdF9jYXNlc30NCmdncGxvdCgNCiAgZGF0YSAlPiUgZmlsdGVyKA0KICAgIGNvbnRpbmVudCAhPSAiU0EiICYNCiAgICAgIHR5cGUgPT0gImNhc2VzIiAmDQogICAgICBjb3VudHJ5X2NvZGUgJWluJSBjb3VudHJ5X2NvZGVzICYNCiAgICAgIGN1bXVsYXRpdmVfY291bnQgPiAwDQogICksDQogIGFlcyh4ID0gZGF0ZSwgeSA9IGN1bXVsYXRpdmVfY291bnQpDQopICsNCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IGNvdW50cnkpLCBzaXplID0gMSkgKw0KICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IGNvbW1hKSArDQogIGdndGl0bGUoIkN1bXVsYXRpdmUgQ2FzZXMgYnkgQ291bnRyeSIpICsNCiAgeGxhYigiRGF0ZSIpICsNCiAgeWxhYigiQ3VtdWxhdGl2ZSBDYXNlcyIpICsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDEpKSArDQogIHNjYWxlX2NvbG9yX2h1ZShsID0gNTApDQpgYGANCg0KQmVsb3cgd2UgcGxvdCB0aGUgY3VtdWxhdGl2ZSBkZWF0aHMgYnkgY291bnRyeSBvbiBhIGxvZyBzY2FsZToNCg0KYGBge3IgcGxvdF9kZWF0aHN9DQpnZ3Bsb3QoDQogIGRhdGEgJT4lIGZpbHRlcigNCiAgICBjb250aW5lbnQgIT0gIlNBIiAmDQogICAgICB0eXBlID09ICJkZWF0aHMiICYNCiAgICAgIGNvdW50cnlfY29kZSAlaW4lIGNvdW50cnlfY29kZXMgJiBjdW11bGF0aXZlX2NvdW50ID4gMA0KICApLA0KICBhZXMoeCA9IGRhdGUsIHkgPSBjdW11bGF0aXZlX2NvdW50KQ0KKSArDQogIGdlb21fbGluZShhZXMoY29sb3IgPSBjb3VudHJ5KSwgc2l6ZSA9IDEpICsNCiAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBjb21tYSkgKw0KICBnZ3RpdGxlKCJDdW11bGF0aXZlIERlYXRocyBieSBDb3VudHJ5IikgKw0KICB4bGFiKCJEYXRlIikgKw0KICB5bGFiKCJDdW11bGF0aXZlIERlYXRocyIpICsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDEpKSArDQogIHNjYWxlX2NvbG9yX2h1ZShsID0gNTApDQpgYGANCg0KQmVsb3cgd2UgcGxvdCBhdmVyYWdlIG1vYmlsaXR5IGluZGV4ZXMgYnkgY291bnRyeToNCg0KYGBge3IgcGxvdF9tb2JpbGl0eX0NCmdncGxvdChkYXRhID0gZ29vZ2xlX21vYmlsaXR5X2RhdGEgJT4lIGZpbHRlcihjb3VudHJ5X2NvZGUgJWluJSBjb3VudHJ5X2NvZGVzKSwNCiAgICAgICBhZXMoeCA9IGRhdGUsIHkgPSBhdmVyYWdlX21vYmlsaXR5LCBjb2xvdXIgPSBjb3VudHJ5KSkgKw0KICBnZW9tX2xpbmUoKSArIHNjYWxlX2NvbG9yX2h1ZShsID0gNTApICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCiMgTWV0aG9kICYgQXNzdW1wdGlvbnMNCg0KVGhpcyBhbmFseXNpcyBmb2xsb3dzIHRoZSBmb2xsb3dzIHRoZSBtZXRob2QgcHJvcG9zZWQgW0BDb3JpMjAxM10uDQoNCkVzc2VudGlhbGx5IHRoaXMgbW9kZWxzIHRoZSBmb2xsb3dpbmcgcmVsYXRpb25zaGlwOg0KDQokRShJX3t0LG19KT1SX3t0LG19XHN1bV97cz0xfV50SV97dC1zLG19d197c30kIHdoZXJlOg0KDQoqICRtJCBpcyB0aGUgY291bnRyeSBvZiBwcm92aW5jZSBpbiBjYXNlIG9mIFNvdXRoIEFmcmljYW4gZGF0YS4NCiogJElfe3QsbX0kIGlzIHRoZSBudW1iZXIgb2YgY2FzZXMgKG9yIGRlYXRocykgcmVwb3J0ZWQgYXQgdGltZSAkdCQuDQoqICRSX3t0LG19JCBpcyB0aGUgImluc3RhbnRhbmVvdXMgcmVwcm9kdWN0aW9uIG51bWJlciIgdG8gcXVvdGUgW0BDb3JpMjAxM10uIA0KKiAkSV97dCxtfSBcc2ltIFBvaXNzb24kIHdpdGggdGhlIGFib3ZlIG1lYW4uDQoqICR3X3tzfSQgaXMgdGhlIGluZmVjdGl2aXR5IHByb2ZpbGUgb2YgYW4gaW5kaXZpZHVhbCBvdmVyIHRpbWUgJHMkLg0KDQpUaGUgZm9ybXVsYXRpb24gb2YgaW5zdGFudGFuZW91cyByYXRlIG9mIHRyYW5zbWlzc2lvbiBwcm9wb3NlZCBpbiBbQENvcmkyMDEzXSBpcyBjb252ZW5pZW50IGJlY2F1c2UgaXQgZW5zdXJlcyB0aGF0ICRSX3t0LG19JCBvbmx5IGRlcGVuZHMgb24gaW5mb3JtYXRpb24gdGhhdCBpcyBrbm93biBhdCB0aW1lICR0JC4gIFdlIGRvIG5vdCBuZWVkIHRvIGtub3cgYWJvdXQgZnV0dXJlIHRyYW5zbWlzc2lvbi4NCg0KIyMgU2VyaWFsIEludGVydmFsDQoNClRvIGRvIGZ1cnRoZXIgYW5hbHlzaXMgYW4gc2VyaWFsIGludGVydmFsIGFzc3VtcHRpb24gaXMgbmVlZGVkICgkd197c30kKS4gIFRoZSBzZXJpYWwgaW50ZXJ2YWwgaXMgdGFrZW4gZnJvbSBbQEZlcmd1c29uMjAyMF0uICBJdCdzIGFzc3VtZWQgdG8gYmUgR2FtbWEgZGlzdHJpYnV0ZWQgd2l0aCBtZWFuIG9mIDYuNDggYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAzLjgzLiAgVGhpcyBjb3JyZXNwb25kcyB0byB0aGUgZWZmZWN0aXZlIGluZmVjdGlvdXNuZXNzIG9mIGFuIGluZGl2aWR1YWwgc2luY2UgYWNxdWlyaW5nIHRoZSBpbmZlY3Rpb24gdGhlbXNlbHZlcy4gIA0KDQpXZSBwbG90IHRoaXMgc2VyaWFsIGRpc3RyaWJ1dGlvbiBiZWxvdzoNCg0KYGBge3Igc2VyaWFsX2ludGVydmFsfQ0Kc2lfbWVhbiA8LSA2LjQ4DQpzaV9zZCA8LSAzLjgzDQpzaV9jdiA8LSBzaV9zZCAvIHNpX21lYW4NCg0Kc2VyaWFsX2ludGVydmFsID0gcmVwKDAsIDEpDQpzZXJpYWxfaW50ZXJ2YWxbMV0gPSAocGdhbW1hQWx0KDEuNSwgc2lfbWVhbiwgc2lfY3YpIC0gcGdhbW1hQWx0KDAsIHNpX21lYW4sIHNpX2N2KSkNCmZvciAoaSBpbiAyOjUwKSB7DQogIHNlcmlhbF9pbnRlcnZhbFtpXSA9IChwZ2FtbWFBbHQoaSArIC41LCBzaV9tZWFuLCBzaV9jdikgLSBwZ2FtbWFBbHQoaSAtIC41LCBzaV9tZWFuLCBzaV9jdikpDQp9DQpnZ3Bsb3QoDQogIGRhdGEuZnJhbWUoZGF5cyA9IHNlcSgxLCAyMCwgMSksIHNlcmlhbF9pbnRlcnZhbCA9IHNlcmlhbF9pbnRlcnZhbFsxOjIwXSksDQogIGFlcyh5ID0gc2VyaWFsX2ludGVydmFsLCB4ID0gZGF5cykNCikgKyBnZW9tX2xpbmUoc2l6ZSA9IDEpICsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDEpKSArDQogIHNjYWxlX2NvbG9yX2h1ZShsID0gNTApDQoNCmBgYA0KDQojIyBUaW1lIFdpbmRvdyBTaXplDQoNCmBgYHtyIHNldF90YXV9DQojIHNsaWRpbmcgd2luZG93DQp0YXUgPC0gNw0KYGBgDQoNCltAQ29yaTIwMTNdIGRlc2NyaWJlcyBhIGNob2ljZSBvZiBhIHRpbWUgd2luZG93IHRvIGVzdGltYXRlICRSX3t0LG19JC4gIEJhc2VkIG9uIHRoaXMgYW5hbHlzaXMgd2UgY2hvc2UgYSB0aW1lIHdpbmRvdyBvZiBgciB0YXVgLWRheXMgKCRcdGF1JCBpbiB1c2luZyBub3RhdGlvbiBvZiBbQENvcmkyMDEzXSkuICBEdXJpbmcgdGhpcyB3aW5kb3cgICRSX3t0LG19JCBpcyBhc3N1bWVkIHRvIGJlIGNvbnN0YW50LiAgSG93ZXZlciBpbmZvcm1hdGlvbiBwcmlvciB0aGF0IGlzIHRha2VuIGludG8gYWNjb3VudC4NCg0KTm90ZSB0aGF0IHRoZSB0aW1lIHdpbmRvdyBzaXplIGRvZXMgbm90IGludGVyYWN0IHdpdGggdGhlIHNlcmlhbCBpbnRlcnZhbCBhcyBpbmZvcm1hdGlvbiBwcmlvciB0byB0aGUgd2luZG93IGlzIHRha2VuIGludG8gYWNjb3VudC4gIEl0J3MganVzdCB0aGUgcGVyaW9kIGR1cmluZyB3aGljaCAkUl97dCxtfSQgaXMgYXNzdW1lZCB0byBiZSBjb25zdGFudC4NCg0KQmFzZWQgb24gdGhlIHRhYmxlIGluIEFwcGVuZGl4IDIgb2YgW0BDb3JpMjAxM10gd2UgYmVsaWV2ZSBhIHdpbmRvdyBvZiBgciB0YXVgLWRheXMgdG8gYmUgcmVhc29uYWJsZSBhcyBsb25nIGFzIHRoYXQgd2luZG93IGNvbnRhaW5zIDEyIG9yIG1vcmUgY2FzZXMgb3IgZGVhdGhzLg0KDQojIEVzdGltYXRlICRSX3t0LG19JA0KDQojIyBFc3RpbWF0aW9uIFJvdXRpbmUNCg0KVGhlIGZvbGxvd2luZyBjb2RlIHdvcmtzIGJhY2t3YXJkcyBhbmQgZml0cyAkUl97dCxtfSQgdmFsdWVzIGZvciB3aG9sZSB3ZWVrcyAoZW5kaW5nIG9uIHRoZSBsYXN0IGRhdGUgaW4gdGhlIGRhdGEpIHVzaW5nIHRoZSBgRXBpRXN0aW1gIHBhY2thZ2UuIFVzaW5nIGRlYXRocyBvciBpbmZlY3Rpb24gdGhlIGFzIHRoZSBjYXNlcyBpbiB0aGUgcHV0cyB0aGUgZXN0aW1hdGUgb2YgJFJfe3QsbX0kIGFzIGF0IHRoZSBkYXRlIG9mIHRoZSBjYXNlcyBiZWluZyB0cmFja2VkLg0KDQpTbyAkdCQgaXMgYmFzZWQgb24gdGhlIGRhdGUgb2YgcmVwb3J0aW5nIChiZSB0aGF0IGNhc2VzIG9yIGRlYXRocykgYW5kICRtJCBpcyB0aGUgY291bnRyeSBvciBwcm92aW5jZS4gVHdvIHZhbHVlcyBhcmUgZXN0aW1hdGVkIGZvciBlYWNoIGNvdW50cnk6DQoNCjEuICRSX3t0LG19XntjYXNlc30kIGlzIHRoZSByZXByb2R1Y3RpdmUgbnVtYmVyIGltcGxpZWQgYnkgdGhlIGNhc2VzIHJlcG9ydGVkIGF0IHRpbWUgJHQkIGluIGNvdW50cnkgJG0kLg0KMi4gJFJfe3QsbX1ee2RlYXRoc30kIGlzIHRoZSByZXByb2R1Y3RpdmUgbnVtYmVyIGltcGxpZWQgYnkgdGhlIGRlYXRocyByZXBvcnRlZCBhdCB0aW1lICR0JCBpbiBjb3VudHJ5ICRtJC4NCg0KTm90ZSB0aGF0IHRoZSB0aW1lIHBlcmlvZHMgYXJlIGxlZnQgdW5hZGp1c3RlZCwgdGhvdWdoIGluIHJlYWxpdHkgdGhlICRSX3t0LG19XntkZWF0aHN9JCBzaG91bGQgYmUgc2hpZnRlZCBiYWNrIGFwcHJveGltYXRlbHkgMiB3ZWVrcyByZWxhdGl2ZSB0byAkUl97dCxtfV57Y2FzZXN9JC4NCg0KYGBge3IgZXN0aW1hdGVfcnR9DQpSdF9kYXRhIDwtIE5VTEwNCmZvciAoYyBpbiBsZXZlbHMoZGF0YSRjb3VudHJ5KSkgew0KICBmb3IgKHQgaW4gbGV2ZWxzKGRhdGEkdHlwZSkpIHsNCiAgICAjIGZpbHRlciBvdXQgY291bnRyeSBkYXRhIG9mIHR5cGUgdA0KICAgIGNfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoY291bnRyeSA9PSBjICYgdHlwZSA9PSB0KQ0KICAgIA0KICAgICMgdmVjdG9yIG9mIGNvdW50IG9mIGNhc2VzL2RlYXRocw0KICAgIEkgPC0gY19kYXRhJGNvdW50DQogICAgDQogICAgIyB0PTEgY29ycmVzcG9uZHMgdG8gdGhpcyBkYXRlOg0KICAgIHQxX2RhdGUgPC0gbWluKGNfZGF0YSRkYXRlKQ0KICAgIA0KICAgICMgb25seSBjb250aW51ZSBpZiBjX2RhdGEkY3VtdWxhdGl2ZV9jb3VudCA+IDEyDQogICAgaWYgKG1heChjX2RhdGEkY3VtdWxhdGl2ZV9jb3VudCkgPj0gMTIpIHsNCiAgICAgICMgdGhlIGRheSBhZnRlciB0aGUgZmlyc3QgY2FzZS9kZWF0aDoNCiAgICAgIHRfc3RhcnRfZGF0ZSA8LQ0KICAgICAgICBtaW4oKGNfZGF0YSAlPiUgZmlsdGVyKGN1bXVsYXRpdmVfY291bnQgPj0gMTIpKSRkYXRlKSArIDENCiAgICAgIHRfc3RhcnQgPC0NCiAgICAgICAgbWluKHNlcSgxLCBsZW5ndGgoSSkpW2NfZGF0YSRjdW11bGF0aXZlX2NvdW50ID49IDEyXSkgKyAxDQogICAgICANCiAgICAgICMgbGFzdCBkYXkgb2YgY2FzZXMvZGVhdGhzDQogICAgICB0X2VuZCA8LSBsZW5ndGgoSSkNCiAgICAgIHRfZW5kX2RhdGUgPC0gIHQxX2RhdGUgKyAodF9lbmQgLSAxKQ0KICAgICAgDQogICAgICAjIGhvdyBtYW55IGZ1bGwgd2Vla3MgZG8gd2UgaGF2ZQ0KICAgICAgZnVsbF93ZWVrcyA8LSBmbG9vcigodF9lbmQgLSB0X3N0YXJ0KSAvIHRhdSkNCiAgICAgIA0KICAgICAgIyBvbmx5IGNvbnRpbnVlIGlmIHdlIGhhdmUgMSBmdWxsIHdlZWsgb3IgbW9yZQ0KICAgICAgaWYgKGZ1bGxfd2Vla3MgPiAwKSB7DQogICAgICAgICMgdGhlbiBkaXZpZGUgcGVyaW9kIGludG8gd2Vla3MNCiAgICAgICAgVC5TdGFydCA8LQ0KICAgICAgICAgIHNlcSh0X2VuZCAtIHRhdSAqIGZ1bGxfd2Vla3MsIHRfZW5kIC0gdGF1LCBieSA9IHRhdSkNCiAgICAgICAgVC5FbmQgPC0gVC5TdGFydCArIHRhdQ0KICAgICAgICANCiAgICAgICAgIyBlc3RpbWF0ZSAkUl97dCxtfV57dHlwZX0kIGZvciBlYWNoIHdlZWs6DQogICAgICAgIGNfUnQgPC0gZXN0aW1hdGVfUigNCiAgICAgICAgICBpbmNpZCA9IEksDQogICAgICAgICAgY29uZmlnID0gbWFrZV9jb25maWcoDQogICAgICAgICAgICB0X3N0YXJ0ID0gVC5TdGFydCwNCiAgICAgICAgICAgIHRfZW5kID0gVC5FbmQsDQogICAgICAgICAgICBtZWFuX3NpID0gc2lfbWVhbiwNCiAgICAgICAgICAgIHN0ZF9zaSA9IHNpX3NkDQogICAgICAgICAgKSwNCiAgICAgICAgICBtZXRob2QgPSAicGFyYW1ldHJpY19zaSINCiAgICAgICAgKSRSDQogICAgICAgIA0KICAgICAgICAjIGNvdW50IGNhc2VzDQogICAgICAgIGNfZGF0YSR3ZWVrIDwtDQogICAgICAgICAgZmxvb3IoYXMubnVtZXJpYyhjX2RhdGEkZGF0ZSAtICh0MV9kYXRlICsgVC5TdGFydFsxXSkpIC8gdGF1KQ0KICAgICAgICBjX2RhdGEkZGQgPC0NCiAgICAgICAgICBjKDAsIGNfZGF0YSRkYXRlWzI6bnJvdyhjX2RhdGEpXSAtIGNfZGF0YSRkYXRlWzE6KG5yb3coY19kYXRhKSAtIDEpXSkNCiAgICAgICAgY19hZ2dfZGF0YSA8LSBjX2RhdGEgJT4lDQogICAgICAgICAgZmlsdGVyKHdlZWsgPj0gMCkgJT4lDQogICAgICAgICAgZ3JvdXBfYnkod2VlaykgJT4lDQogICAgICAgICAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKGNvdW50KSwgLmdyb3VwcyA9ICJkcm9wIikNCiAgICAgICAgDQogICAgICAgICMgYWRkIGNvdW50cnkgYW5kIHR5cGUgZGVzaWduYXRpb25zDQogICAgICAgIGNfUnQgPC0NCiAgICAgICAgICBjYmluZChjX2RhdGEkY29udGluZW50WzFdLA0KICAgICAgICAgICAgICAgIGNfZGF0YSRjb3VudHJ5X2NvZGVbMV0sDQogICAgICAgICAgICAgICAgYywNCiAgICAgICAgICAgICAgICB0LA0KICAgICAgICAgICAgICAgIGNfUnQsDQogICAgICAgICAgICAgICAgY19hZ2dfZGF0YSRjb3VudCkNCiAgICAgICAgDQogICAgICAgICMgYW5kIGFkZCBkYXRlcw0KICAgICAgICBjX1J0JGRhdGVfc3RhcnQgPC0gdDFfZGF0ZSArIGNfUnQkdF9zdGFydA0KICAgICAgICBjX1J0JGRhdGVfZW5kIDwtIHQxX2RhdGUgKyBjX1J0JHRfZW5kIC0gMQ0KICAgICAgICANCiAgICAgICAgIyBjb21iaW5lIHRoZSByZXN1bHRzDQogICAgICAgIGlmIChpcy5udWxsKFJ0X2RhdGEpKSB7DQogICAgICAgICAgUnRfZGF0YSA8LSBjX1J0DQogICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgUnRfZGF0YSA8LSByYmluZChSdF9kYXRhLCBjX1J0KQ0KICAgICAgICB9DQogICAgICB9DQogICAgfQ0KICB9DQp9DQpgYGANCg0KIyMgUHJlcHBpbmcgZGF0YSBmb3IgdGFidWxhdGlvbiBhbmQgcGxvdHMNCg0KQmVsb3cgd2UgcHJlcCBjb2x1bW4gaGVhZGluZ3MgYW5kIHByZXBhcmUgQ0kgZGF0YToNCg0KYGBge3IgcHJlcF9ydF9kYXRhfQ0KIyBDb2x1bW4gbmFtZXMNCmNvbG5hbWVzKFJ0X2RhdGEpIDwtIGMoDQogICJjb250aW5lbnQiLA0KICAiY291bnRyeV9jb2RlIiwNCiAgImNvdW50cnkiLA0KICAidHlwZSIsDQogICJ0X3N0YXJ0IiwNCiAgInRfZW5kIiwNCiAgIlJ0X21lYW4iLA0KICAiUnRfc3RkIiwNCiAgIlJ0X2xpXzk1IiwNCiAgIlJ0X2xpXzkwIiwNCiAgIlJ0X2xpXzUwIiwNCiAgIlJ0X21lZGlhbiIsDQogICJSdF91aV81MCIsDQogICJSdF91aV85MCIsDQogICJSdF91aV85NSIsDQogICJjb3VudCIsDQogICJkYXRlX3N0YXJ0IiwNCiAgImRhdGVfZW5kIg0KKQ0KDQojIG1pZCB3ZWVrIGRhdGEgcG9pbnQNClJ0X2RhdGEkZGF0ZV9taWQgPC0NCiAgUnRfZGF0YSRkYXRlX3N0YXJ0ICsgKFJ0X2RhdGEkZGF0ZV9lbmQgLSBSdF9kYXRhJGRhdGVfc3RhcnQpIC8gMg0KDQojIHNwbGl0IG91dCBDSSBkYXRhIGludG8gZGlmZmVyZW50IGRhdGFzZXQNClJ0X2NpX2RhdGEgPC0gTlVMTA0KZm9yIChjaSBpbiBjKCI1MCIsICI5MCIsICI5NSIpKSB7DQogIHIgPC0gZGF0YS5mcmFtZSgNCiAgICBjb3VudHJ5X2NvZGUgPSBSdF9kYXRhJGNvdW50cnlfY29kZSwNCiAgICBjb3VudHJ5ID0gUnRfZGF0YSRjb3VudHJ5LA0KICAgIHR5cGUgPSBSdF9kYXRhJHR5cGUsDQogICAgY2kgPSByZXAocGFzdGUwKGNpLCAiJSIpLCBucm93KFJ0X2RhdGEpKSwNCiAgICBkYXRlX21pZCA9IFJ0X2RhdGEkZGF0ZV9taWQsDQogICAgUnRfbWVhbiA9IFJ0X2RhdGEkUnRfbWVhbiwNCiAgICBSdF9saSA9IFJ0X2RhdGFbW3Bhc3RlMCgiUnRfbGlfIiwgY2kpXV0sDQogICAgUnRfdWkgPSBSdF9kYXRhW1twYXN0ZTAoIlJ0X3VpXyIsIGNpKV1dDQogICkNCiAgUnRfY2lfZGF0YSA8LSByYmluZChyLCBSdF9jaV9kYXRhKQ0KfQ0KDQojIEZhY3RvcnMNClJ0X2RhdGEkdHlwZSA8LSBhcy5mYWN0b3IoUnRfZGF0YSR0eXBlKQ0KUnRfZGF0YSRjb3VudHJ5IDwtIGFzLmZhY3RvcihSdF9kYXRhJGNvdW50cnkpDQoNCiMgcmVtb3ZlIHdoYXQgaXMgbm90IG5lZWRlZA0Kcm0obGlzdCA9IGxzKClbIWxzKCkgJWluJSBjKCJkYXRhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiY291bnRyeV9jb2RlcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJ0X2RhdGEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSdF9jaV9kYXRhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ29vZ2xlX21vYmlsaXR5X2RhdGEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0YXUiKV0pDQpgYGANCg0KIyBMaW5raW5nICRSX3t0LG19JCBlc3RpbWF0ZXMgdG8gbW9iaWxpdHkNCg0KIyMgUHJlcGFyaW5nIGBSdF9kYXRhYA0KDQpMaW5raW5nIHRoZSBtb2JpbGl0eSBkYXRhIHNob3VsZCBiZSBkb25lIGluIGEgd2F5IHRoYXQgY29ycmVzcG9uZHMgdG8gdGhlIHRpbWUgdGhlIGluZmVjdGlvbiBzaG91bGQgaGF2ZSBvY2N1cnJlZCBub3QgdGhlIHRpbWUgb2YgZGVhdGguICBCZWxvdyBuZXcgZGF0ZSByYW5nZXMgYXJlIGNhbGN1bGF0ZWQgdG8gY29ycmVzcG9uZCByb3VnaGx5IHRvIHRoZSB0aW1lIG9mIGluZmVjdGlvbjoNCg0KYGBge3IgY2FsY19kYXRlc19pbmZlY3Rpb259DQpSdF9kYXRhJGRhdGVfaW5mX3N0YXJ0IDwtIFJ0X2RhdGEkZGF0ZV9zdGFydCAtIGlmZWxzZShSdF9kYXRhJHR5cGU9PSJkZWF0aCIsMjMrNyw2KzcpDQpSdF9kYXRhJGRhdGVfaW5mX2VuZCA8LSBSdF9kYXRhJGRhdGVfZW5kIC0gaWZlbHNlKFJ0X2RhdGEkdHlwZT09ImRlYXRoIiwyMys3LDYrNykNClJ0X2RhdGEkZGF0ZV9pbmZfbWlkIDwtIFJ0X2RhdGEkZGF0ZV9taWQgLSBpZmVsc2UoUnRfZGF0YSR0eXBlPT0iZGVhdGgiLDIzKzcsNis3KQ0KYGBgDQoNCg0KIyMgQ2FsY3VsYXRpbmcgd2Vla2x5IG1vYmlsaXR5IGRhdGENCg0KVGhlIGNvZGUgYmVsb3cgY2FsY3VsYXRlcyBhdmVyYWdlIHdlZWtseSBtb2JpbGl0eSBkYXRhIGF2ZXJhZ2VkIG92ZXIgdGhlIHNhbWUgZGF0ZSByYW5nZXMgYXMgY2FsY3VsYXRlZCBhYm92ZS4NCg0KYGBge3IgY2FsY193ZWVrbHlfbW9iaWxpdHl9DQp3ZWVrbHlfbW9iaWxpdHlfZGF0YSA8LSBOVUxMDQpjb21iaW5lZF9jb3VudHJ5X2NvZGVzIDwtDQogIGxldmVscyhSdF9kYXRhJGNvdW50cnlfY29kZSlbbGV2ZWxzKFJ0X2RhdGEkY291bnRyeV9jb2RlKSAlaW4lIGxldmVscyhnb29nbGVfbW9iaWxpdHlfZGF0YSRjb3VudHJ5X2NvZGUpXQ0KZm9yIChjIGluIGNvbWJpbmVkX2NvdW50cnlfY29kZXMpIHsNCiAgZm9yICh0IGluIGxldmVscyhSdF9kYXRhJHR5cGUpKSB7DQogICAgY19kYXRhIDwtIFJ0X2RhdGEgJT4lIGZpbHRlcihjb3VudHJ5X2NvZGUgPT0gYyAmIHR5cGUgPT0gdCkNCiAgICBpZiAobnJvdyhjX2RhdGEpID4gMCkgew0KICAgICAgZm9yIChpIGluIDE6bnJvdyhjX2RhdGEpKSB7DQogICAgICAgIHdfZGF0YSA8LSBnb29nbGVfbW9iaWxpdHlfZGF0YSAlPiUgZmlsdGVyKGNvdW50cnlfY29kZSA9PSBjICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlID49IGNfZGF0YSRkYXRlX2luZl9zdGFydFtpXSAmDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA8PSBjX2RhdGEkZGF0ZV9pbmZfZW5kW2ldKSAlPiUNCiAgICAgICAgICBncm91cF9ieShjb3VudHJ5X2NvZGUpICU+JQ0KICAgICAgICAgIHN1bW1hcmlzZSgNCiAgICAgICAgICAgIHJldGFpbF9hbmRfcmVjcmVhdGlvbiA9IG1lYW4ocmV0YWlsX2FuZF9yZWNyZWF0aW9uKSwNCiAgICAgICAgICAgIGdyb2NlcnlfYW5kX3BoYXJtYWN5ID0gbWVhbihncm9jZXJ5X2FuZF9waGFybWFjeSksDQogICAgICAgICAgICBwYXJrcyA9IG1lYW4ocGFya3MpLA0KICAgICAgICAgICAgdHJhbnNpdF9zdGF0aW9ucyA9IG1lYW4odHJhbnNpdF9zdGF0aW9ucyksDQogICAgICAgICAgICB3b3JrcGxhY2VzID0gbWVhbih3b3JrcGxhY2VzKSwNCiAgICAgICAgICAgIHJlc2lkZW50aWFsID0gbWVhbihyZXNpZGVudGlhbCksDQogICAgICAgICAgICBhdmVyYWdlX21vYmlsaXR5ID0gbWVhbihhdmVyYWdlX21vYmlsaXR5KSwNCiAgICAgICAgICAgIC5ncm91cHMgPSAiZHJvcCINCiAgICAgICAgICApDQogICAgICAgIGlmIChucm93KHdfZGF0YSkgPiAwKSB7DQogICAgICAgICAgaWYgKCFpcy5uYSh3X2RhdGEkY291bnRyeV9jb2RlKSkgew0KICAgICAgICAgICAgd19kYXRhJHR5cGUgPC0gdA0KICAgICAgICAgICAgd19kYXRhJGRhdGVfaW5mX21pZCA8LSBjX2RhdGEkZGF0ZV9pbmZfbWlkW2ldDQogICAgICAgICAgICB3ZWVrbHlfbW9iaWxpdHlfZGF0YSA8LQ0KICAgICAgICAgICAgICByYmluZCh3ZWVrbHlfbW9iaWxpdHlfZGF0YSwgd19kYXRhKQ0KICAgICAgICAgIH0NCiAgICAgICAgfQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KfQ0KYGBgDQoNCiMjIEpvaW5pbmcgd2l0aCAkUl97dCxtfSQgZXN0aW1hdGVzDQoNCkJhc2VkIG9uIHdvcmsgYWJvdmUgdGhlIHdlZWtseSBtb2JpbGl0eSBkYXRhIGNhbiBiZSBqb2luZWQ6DQpgYGB7ciBqb2luX21vYmlsaXR5fQ0KUnRfZGF0YSA8LQ0KICBsZWZ0X2pvaW4oUnRfZGF0YSwNCiAgICAgICAgICAgIHdlZWtseV9tb2JpbGl0eV9kYXRhLA0KICAgICAgICAgICAgYnkgPSBjKCJjb3VudHJ5X2NvZGUiLCAidHlwZSIsICJkYXRlX2luZl9taWQiKSkNCmBgYA0KDQojIE1vZGVsbGluZyAkUl97dCxtfSQgYXMgYSBmdW5jdGlvbiBvZiBNb2JpbGl0eSBJbmRleGVzDQoNCioqVE8gRE8qKg0KDQojIFJlc3VsdHMNCg0KIyMgQ3VycmVudCAkUl97dCxtfSQgZXN0aW1hdGVzDQoNCkJlbG93IGN1cnJlbnQgKGxhc3Qgd2Vla2x5KSAkUl97dCxtfSQgZXN0aW1hdGVzIGFyZSB0YWJ1bGF0ZWQuICBUbyBzbyB0aGUgZGF0YSBmaXJzdCBuZWVkcyB0byBiZSBwcmVwYXJlZDoNCg0KYGBge3IgcHJlcF90YWJsZX0NCiMgZmluZCB0aGUgbGFzdCBlc3RpbWF0ZXMNCmxhc3RfZGF0ZXMgPC0gUnRfZGF0YSAlPiUNCiAgZ3JvdXBfYnkoY291bnRyeSwgdHlwZSkgJT4lDQogIHN1bW1hcmlzZShkYXRlX21pZCA9IG1heChkYXRlX21pZCksIC5ncm91cHMgPSAiZHJvcCIpDQoNCiMgY29uc3RydWN0IGEgdGFibGUgd2l0aCBuaWNlIGZpZWxkcyBuYW1lcw0KdGFibGUgPC0NCiAgaW5uZXJfam9pbihsYXN0X2RhdGVzLCBSdF9kYXRhLCBieSA9IGMoImNvdW50cnkiLCAidHlwZSIsICJkYXRlX21pZCIpKQ0KdGFibGUgPC0gdGFibGUgJT4lDQogIHNlbGVjdChjb250aW5lbnQsDQogICAgICAgICBjb3VudHJ5X2NvZGUsDQogICAgICAgICBjb3VudHJ5LA0KICAgICAgICAgdHlwZSwNCiAgICAgICAgIGNvdW50LA0KICAgICAgICAgZGF0ZV9lbmQsDQogICAgICAgICBSdF9saV85NSwNCiAgICAgICAgIFJ0X21lYW4sDQogICAgICAgICBSdF91aV85NSkNCmBgYA0KDQojIyMgU291dGggQWZyaWNhDQoNCmBgYHtyIHRhYnVsYXRlX3phfQ0Ka25pdHI6OmthYmxlKCh0YWJsZSAlPiUgZmlsdGVyKGNvbnRpbmVudCA9PSAiU0EiKSlbLCBjKC0xLC0yKV0sDQogICAgICAgICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gJywnKSwNCiAgICAgICAgICAgICBkaWdpdHMgPSAxLA0KICAgICAgICAgICAgIGNvbC5uYW1lcyA9ICBjKA0KICAgICAgICAgICAgICAgIkNvdW50cnkiLA0KICAgICAgICAgICAgICAgIkVzdGltYXRlZCBUeXBlIiwNCiAgICAgICAgICAgICAgICJDb3VudCAoTGFzdCBXZWVrKSIsDQogICAgICAgICAgICAgICAiV2VlayBFbmRpbmciLA0KICAgICAgICAgICAgICAgIlIgLSBMb3dlciBDSSIsDQogICAgICAgICAgICAgICAiUiAtIE1lYW4iLA0KICAgICAgICAgICAgICAgIlIgLSBVcHBwZXIgQ0kiDQogICAgICAgICAgICAgKQ0KKQ0KYGBgDQoNCiMjIyBPdGhlciBDb3VudHJpZXMNCg0KVGhlIHRhYmxlIGJlbG93IGNvbnRhaW5zICRSX3t0LG19JCBlc3RpbWF0ZXMgZm9yIHRoZSBsYXN0IHdlZWsgYXZhaWxhYmxlLiAgVGhlc2UgZXN0aW1hdGVzIGFyZSBiYXNlZCBlaXRoZXIgb24gY2FzZXMgb3IgZGVhdGhzIGFuZCBhIDk1JSBjb25maWRlbmNlIGludGVydmFsIGlzIHNob3duLg0KDQpgYGB7ciB0YWJ1bGF0ZX0NCmtuaXRyOjprYWJsZSgodGFibGUgJT4lIGZpbHRlcihjb250aW5lbnQgIT0gIlNBIiAmIGNvdW50cnlfY29kZSAlaW4lIGNvdW50cnlfY29kZXMpKVssIGMoLTEsLTIpXSwNCiAgICAgICAgICAgICBmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAnLCcpLA0KICAgICAgICAgICAgIGRpZ2l0cyA9IDEsDQogICAgICAgICAgICAgY29sLm5hbWVzID0gIGMoDQogICAgICAgICAgICAgICAiQ291bnRyeSIsDQogICAgICAgICAgICAgICAiRXN0aW1hdGVkIFR5cGUiLA0KICAgICAgICAgICAgICAgIkNvdW50IChMYXN0IFdlZWspIiwNCiAgICAgICAgICAgICAgICJXZWVrIEVuZGluZyIsDQogICAgICAgICAgICAgICAiUiAtIExvd2VyIENJIiwNCiAgICAgICAgICAgICAgICJSIC0gTWVhbiIsDQogICAgICAgICAgICAgICAiUiAtIFVwcHBlciBDSSINCiAgICAgICAgICAgICApDQopDQpgYGANCg0KIyMjIFRvcCAxMCBDb3VudHJpZXMNCg0KQmVsb3cgd2Ugc2hvdyB2YXJpb3VzIGV4dHJlbWVzIG9mICRSX3t0LG19JCB3aGVyZSBjb3VudHMgKGRlYXRocyBvciBjYXNlcykgZXhjZWVkIDUwIGluIHRoZSBsYXN0IHdlZWsuDQoNCiMjIyMgTG93ZXN0ICRSX3t0LG19JCBiYXNlZCBvbiBEZWF0aHMNCg0KYGBge3IgdGFidWxhdGVfbG93X2RlYXRoc30NCmtuaXRyOjprYWJsZSgodGFibGUgJT4lIGZpbHRlcihjb250aW5lbnQgIT0gIlNBIiAmIHR5cGU9PSJkZWF0aHMiICYgY291bnQ+NTApICU+JQ0KICAgICAgICAgICAgICAgIGFycmFuZ2UoUnRfbWVhbikpWzE6MTAsIGMoLTEsLTIpXSwNCiAgICAgICAgICAgICBmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAnLCcpLA0KICAgICAgICAgICAgIGRpZ2l0cyA9IDEsDQogICAgICAgICAgICAgY29sLm5hbWVzID0gIGMoDQogICAgICAgICAgICAgICAiQ291bnRyeSIsDQogICAgICAgICAgICAgICAiRXN0aW1hdGVkIFR5cGUiLA0KICAgICAgICAgICAgICAgIkNvdW50IChMYXN0IFdlZWspIiwNCiAgICAgICAgICAgICAgICJXZWVrIEVuZGluZyIsDQogICAgICAgICAgICAgICAiUiAtIExvd2VyIENJIiwNCiAgICAgICAgICAgICAgICJSIC0gTWVhbiIsDQogICAgICAgICAgICAgICAiUiAtIFVwcHBlciBDSSINCiAgICAgICAgICAgICApDQopDQpgYGANCg0KIyMjIyBMb3dlc3QgJFJfe3QsbX0kIGJhc2VkIG9uIENhc2VzDQoNCmBgYHtyIHRhYnVsYXRlX2xvd19jYXNlc30NCmtuaXRyOjprYWJsZSgodGFibGUgJT4lIGZpbHRlcihjb250aW5lbnQgIT0gIlNBIiAmIHR5cGU9PSJjYXNlcyIgJiBjb3VudD41MCkgJT4lDQogICAgICAgICAgICAgICAgYXJyYW5nZShSdF9tZWFuKSlbMToxMCwgYygtMSwtMildLA0KICAgICAgICAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICcsJyksDQogICAgICAgICAgICAgZGlnaXRzID0gMSwNCiAgICAgICAgICAgICBjb2wubmFtZXMgPSAgYygNCiAgICAgICAgICAgICAgICJDb3VudHJ5IiwNCiAgICAgICAgICAgICAgICJFc3RpbWF0ZWQgVHlwZSIsDQogICAgICAgICAgICAgICAiQ291bnQgKExhc3QgV2VlaykiLA0KICAgICAgICAgICAgICAgIldlZWsgRW5kaW5nIiwNCiAgICAgICAgICAgICAgICJSIC0gTG93ZXIgQ0kiLA0KICAgICAgICAgICAgICAgIlIgLSBNZWFuIiwNCiAgICAgICAgICAgICAgICJSIC0gVXBwcGVyIENJIg0KICAgICAgICAgICAgICkNCikNCmBgYA0KIyMjIyBIaWdoZXN0ICRSX3t0LG19JCBiYXNlZCBvbiBEZWF0aHMNCg0KYGBge3IgdGFidWxhdGVfaGlnaF9kZWF0aHN9DQprbml0cjo6a2FibGUoKHRhYmxlICU+JSBmaWx0ZXIoY29udGluZW50ICE9ICJTQSIgJiB0eXBlPT0iZGVhdGhzIiAmIGNvdW50PjUwKSAlPiUNCiAgICAgICAgICAgICAgICBhcnJhbmdlKC1SdF9tZWFuKSlbMToxMCwgYygtMSwtMildLA0KICAgICAgICAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICcsJyksDQogICAgICAgICAgICAgZGlnaXRzID0gMSwNCiAgICAgICAgICAgICBjb2wubmFtZXMgPSAgYygNCiAgICAgICAgICAgICAgICJDb3VudHJ5IiwNCiAgICAgICAgICAgICAgICJFc3RpbWF0ZWQgVHlwZSIsDQogICAgICAgICAgICAgICAiQ291bnQgKExhc3QgV2VlaykiLA0KICAgICAgICAgICAgICAgIldlZWsgRW5kaW5nIiwNCiAgICAgICAgICAgICAgICJSIC0gTG93ZXIgQ0kiLA0KICAgICAgICAgICAgICAgIlIgLSBNZWFuIiwNCiAgICAgICAgICAgICAgICJSIC0gVXBwcGVyIENJIg0KICAgICAgICAgICAgICkNCikNCmBgYA0KDQojIyMjIEhpZ2hlc3QgJFJfe3QsbX0kIGJhc2VkIG9uIENhc2VzDQoNCmBgYHtyIHRhYnVsYXRlX2hpZ2hfY2FzZXN9DQprbml0cjo6a2FibGUoKHRhYmxlICU+JSBmaWx0ZXIoY29udGluZW50ICE9ICJTQSIgJiB0eXBlPT0iY2FzZXMiICYgY291bnQ+NTApICU+JQ0KICAgICAgICAgICAgICAgIGFycmFuZ2UoLVJ0X21lYW4pKVsxOjEwLCBjKC0xLC0yKV0sDQogICAgICAgICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gJywnKSwNCiAgICAgICAgICAgICBkaWdpdHMgPSAxLA0KICAgICAgICAgICAgIGNvbC5uYW1lcyA9ICBjKA0KICAgICAgICAgICAgICAgIkNvdW50cnkiLA0KICAgICAgICAgICAgICAgIkVzdGltYXRlZCBUeXBlIiwNCiAgICAgICAgICAgICAgICJDb3VudCAoTGFzdCBXZWVrKSIsDQogICAgICAgICAgICAgICAiV2VlayBFbmRpbmciLA0KICAgICAgICAgICAgICAgIlIgLSBMb3dlciBDSSIsDQogICAgICAgICAgICAgICAiUiAtIE1lYW4iLA0KICAgICAgICAgICAgICAgIlIgLSBVcHBwZXIgQ0kiDQogICAgICAgICAgICAgKQ0KKQ0KYGBgDQoNCiMjIFJlc3VsdCBQbG90cw0KDQpMZXQncyBnZW5lcmF0ZSBvdXIgcGxvdHMgZm9yIGVhY2ggY291bnRyeS9wcm92aW5jZSBpbiBhIGxpc3QuICBXZSBmaWx0ZXIgb3V0IHdlZWtzIHdoZXJlIHRoZSB1cHBlciBlbmQgb2YgY29uZmlkZW5jZSBpbnRlcnZhbCBmb3IgJFJfe3QsbX0kIGV4Y2VlZHMgZml2ZS4NCg0KYGBge3IgcmVzdWx0X3Bsb3RzfQ0KY291bnRyeV9wbG90cyA8LSBsaXN0KCkNCmZvciAoYyBpbiBsZXZlbHMoUnRfZGF0YSRjb3VudHJ5KSkgew0KICBwMSA8LSBnZ3Bsb3QoUnRfY2lfZGF0YSAlPiUgZmlsdGVyKGNvdW50cnkgPT0gYyAmDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaSA9PSAiOTUlIiAmDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSdF91aSA8PSA1KSwNCiAgICAgICAgICAgICAgIGFlcyh4ID0gZGF0ZV9taWQsIHkgPSBSdF9tZWFuKSkgKw0KICAgIGdlb21fY3Jvc3NiYXIoDQogICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlMihwYWRkaW5nID0gMCksDQogICAgICBhZXMoDQogICAgICAgIHltaW4gPSBSdF9saSwNCiAgICAgICAgeW1heCA9IFJ0X3VpLA0KICAgICAgICBjb2xvdXIgPSB0eXBlLA0KICAgICAgICBmaWxsID0gdHlwZQ0KICAgICAgKSwNCiAgICAgIHdpZHRoID0gdGF1DQogICAgKSArDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhhbHBoYSgiZGVlcHNreWJsdWU0IiwgMC40NSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSgiIzhiMDA2OCIsIDAuNDUpKSkgKw0KICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhhbHBoYSgiZGVlcHNreWJsdWU0IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhKCIjOGIwMDY4IikpKSArDQogICAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgNSkpICsNCiAgICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aHMiLCBsYWJlbHMgPSBkYXRlX2Zvcm1hdCgiJWUgJWIiKSkgKw0KICAgIHRoZW1lX2J3KCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKw0KICAgIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAxKSkgKw0KICAgIHhsYWIoIldlZWsiKSArDQogICAgeWxhYihleHByZXNzaW9uKFJbdCwgbV0pKSAgKw0KICAgIGdndGl0bGUoYykgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxKQ0KICBjb3VudHJ5X3Bsb3RzW1tjXV0gPC0gcDENCn0NCmBgYA0KDQoNCiMjIyBTb3V0aCBBZnJpY2ENCg0KQmVsb3cgd2UgcGxvdCBlc3RpbWF0ZXMgZm9yIGFsbCB0aGUgcHJvdmluY2VzLiAgTm90ZSB0aGF0IFtAQ29yaTIwMTNdIHJlY29tbWVuZHMgb25seSB0byBzdGFydCB1c2luZyBlc3RpbWF0ZXMgYWZ0ZXIgYXQgbGVhc3QgMTIgY2FzZXMgaGF2ZSBvY2N1cnJlZC4gIE9uIHRoYXQgYmFzaXMgd2UgZG8gbm90IGhhdmUgZXN0aW1hdGVzIGZvciBSIGJhc2VkIG9uIGRlYXRocyBmb3IgYSBudW1iZXIgb2YgcHJvdmluY2VzLg0KDQpgYGB7ciBwbG90X3JfcHJvdmluY2VzLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9DQpnZ2FycmFuZ2UoDQogIGNvdW50cnlfcGxvdHNbWyJFQyJdXSwNCiAgY291bnRyeV9wbG90c1tbIkZTIl1dLA0KICBjb3VudHJ5X3Bsb3RzW1siR1AiXV0sDQogIGNvdW50cnlfcGxvdHNbWyJLWk4iXV0sDQogIGNvdW50cnlfcGxvdHNbWyJMUCJdXSwNCiAgY291bnRyeV9wbG90c1tbIk1QIl1dLA0KICBjb3VudHJ5X3Bsb3RzW1siTkMiXV0sDQogIGNvdW50cnlfcGxvdHNbWyJOVyJdXSwNCiAgY291bnRyeV9wbG90c1tbIldDIl1dLA0KICBjb3VudHJ5X3Bsb3RzW1siU0EiXV0sDQogIG5jb2wgPSAyLA0KICBucm93ID0gNQ0KKSArDQogIGdndGl0bGUoIlJlcHJvZHVjdGl2ZSBudW1iZXIgYnkgcHJvdmluY2UsIHdpdGggOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIikNCmBgYA0KDQojIyMgT3RoZXIgQ291bnRyaWVzDQoNCkJlbG93IHdlIHBsb3QgYWxsIHRoZSBjb3VudHJpZXM6DQoNCmBgYHtyIHBsb3Rfcl9jb3VudHJpZXMsIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMn0NCmdnYXJyYW5nZSgNCiAgY291bnRyeV9wbG90c1tbIkJyYXppbCJdXSwNCiAgY291bnRyeV9wbG90c1tbIkNhbmFkYSJdXSwNCiAgY291bnRyeV9wbG90c1tbIkNoaWxlIl1dLA0KICBjb3VudHJ5X3Bsb3RzW1siSW5kaWEiXV0sDQogIGNvdW50cnlfcGxvdHNbWyJJcmVsYW5kIl1dLA0KICBjb3VudHJ5X3Bsb3RzW1siSXRhbHkiXV0sDQogIGNvdW50cnlfcGxvdHNbWyJQZXJ1Il1dLA0KICBjb3VudHJ5X3Bsb3RzW1siU291dGhfQWZyaWNhIl1dLA0KICBjb3VudHJ5X3Bsb3RzW1siU3BhaW4iXV0sDQogIGNvdW50cnlfcGxvdHNbWyJVbml0ZWRfS2luZ2RvbSJdXSwNCiAgbmNvbCA9IDIsDQogIG5yb3cgPSA1DQopICsNCiAgZ2d0aXRsZSgiUmVwcm9kdWN0aXZlIG51bWJlciBieSBjb3VudHJ5LCB3aXRoIDk1JSBjb25maWRlbmNlIGludGVydmFscyIpDQpgYGANCg0KIyMjIFNjYXR0ZXIgUGxvdHMgb2YgJFJfe3QsbX1ee2Nhc2VzfSQgdnMuICRSX3t0LG19XntkZWF0aHN9JA0KDQpCZWxvdyB3ZSBwbG90IGVzdGltYXRlcyBvZiAkUl97dCxtfSQgYmFzZWQgb24gY2FzZXMgdmVyc3VzIHRob3NlIGJhc2VkIG9uIGRlYXRocyBmb3IgY291bnRyaWVzIHdoZXJlIHRoZXNlIGVzdGltYXRlcyByYW5nZSBiZXR3ZWVuIDAuNSBhbmQgMi4NCg0KIyMjIyBTb3V0aCBBZnJpY2ENCg0KQmVsb3cgd2UgcGxvdCB3aGVyZSB0aGVyZSB3ZXJlIG1vcmUgdGhhbiA1IGRlYXRocyBhbmQgY2FzZXMgaW4gdGhlIGxhc3Qgd2Vlay4gIEl0IGFwcGVhcnMgdGhhdCwgZ2VuZXJhbGx5LCAkUl97dCxtfV57Y2FzZXN9JCBhbmQgJFJfe3QsbX1ee2RlYXRoc30kIGFyZSBjb25zaXN0ZW50Lg0KDQpgYGB7ciBzY2F0dGVyX3Bsb3RfemF9DQpzcF9kYXRhIDwtIHBpdm90X3dpZGVyKA0KICB0YWJsZSAlPiUgZmlsdGVyKGNvdW50Pj01ICYgY29udGluZW50ID09ICJTQSIpLCANCiAgaWRfY29scz1jKCJjb250aW5lbnQiLCJjb3VudHJ5IiksDQogIG5hbWVzX2Zyb20gPSAidHlwZSIsDQogIHZhbHVlc19mcm9tID0gIlJ0X21lYW4iDQopDQoNCmdncGxvdCgNCiAgc3BfZGF0YSwNCiAgYWVzKHggPSBjYXNlcywgeSA9IGRlYXRocykpICsNCiAgZ2VvbV9wb2ludChhZXMobGFiZWw9Y291bnRyeSkpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1jb3VudHJ5KSxoanVzdD0wLCB2anVzdD0wLCBzaXplPTIpICsNCiAgZ2d0aXRsZSgiUmVwcm9kdWN0aXZlIG51bWJlciBieSBjb3VudHJ5IikgKw0KICB4bGFiKGV4cHJlc3Npb24oUlt0LG1dXmNhc2VzKSkgKw0KICB5bGFiKGV4cHJlc3Npb24oUlt0LG1dXmRlYXRocykpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMC41LCAzKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLjUsIDMpKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAxKSkgKw0KICAgIHNjYWxlX2NvbG9yX2h1ZShsID0gNTApICsNCiAgZ2VvbV9hYmxpbmUoYT0wLGI9MSkNCmBgYA0KDQojIyMjIE90aGVyIGNvdW50cmllcw0KDQpCZWxvdyB3ZSBwbG90IHdoZXJlIHRoZXJlIHdlcmUgbW9yZSB0aGFuIDI1IGRlYXRocyBhbmQgY2FzZXMgaW4gdGhlIGxhc3Qgd2Vlay4gIEl0IGFwcGVhcnMgdGhhdCwgZ2VuZXJhbGx5LCAkUl97dCxtfV57Y2FzZXN9JCBhbmQgJFJfe3QsbX1ee2RlYXRoc30kIGFyZSBjb25zaXN0ZW50Lg0KDQpgYGB7ciBzY2F0dGVyX3Bsb3R9DQpzcF9kYXRhIDwtIHBpdm90X3dpZGVyKA0KICB0YWJsZSAlPiUgZmlsdGVyKGNvdW50Pj0yNSAmIGNvbnRpbmVudCAhPSAiU0EiKSwgDQogIGlkX2NvbHM9YygiY29udGluZW50IiwiY291bnRyeSIpLA0KICBuYW1lc19mcm9tID0gInR5cGUiLA0KICB2YWx1ZXNfZnJvbSA9ICJSdF9tZWFuIg0KKQ0KDQpnZ3Bsb3QoDQogIHNwX2RhdGEsDQogIGFlcyh4ID0gY2FzZXMsIHkgPSBkZWF0aHMpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gY29udGluZW50LCBsYWJlbD1jb3VudHJ5KSkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsPWNvdW50cnkpLGhqdXN0PTAsIHZqdXN0PTAsIHNpemU9MikgKw0KICBnZ3RpdGxlKCJSZXByb2R1Y3RpdmUgbnVtYmVyIGJ5IGNvdW50cnkiKSArDQogIHhsYWIoZXhwcmVzc2lvbihSW3QsbV1eY2FzZXMpKSArDQogIHlsYWIoZXhwcmVzc2lvbihSW3QsbV1eZGVhdGhzKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLjUsIDIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAuNSwgMikpICsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDEpKSArDQogICAgc2NhbGVfY29sb3JfaHVlKGwgPSA1MCkgKw0KICBnZW9tX2FibGluZShhPTAsYj0xKQ0KYGBgDQoNCiMjIE1vYmlsaXR5DQoNCiMjIyBTaW1wbGUgcGxvdA0KDQpCZWxvdyB3ZSBwbG90IHdlZWtzIG9mIGVzdGltYXRlcyBvZiAkUl97dCxtfSQgdmVyc3VzIHRoZSBhdmVyYWdlIG1vYmlsaXR5IGluZGV4IHRoYXQgd2FzIGNvbnN0cnVjdGVkLiBUaGVyZSBpcyBhIGNsZWFyIGxpbmsgYmV0d2VlbiBtb2JpbGl0eSBhbmQgdGhlIHJlcHJvZHVjdGl2ZSBudW1iZXIuICBBcyBpdCBkZWNyZWFzZXMgJFJfe3QsbX0kIHJlZHVjZXM6DQoNCmBgYHtyIHBsb3RfbW9iaWxpdHlfdnNfUnRfZGVhdGhzfQ0KZ2dwbG90KA0KICBSdF9kYXRhICU+JSBmaWx0ZXIodHlwZT09ImNhc2VzIiAmIGNvdW50PjEwMCAmIA0KICAgICAgICAgICAgICAgICAgICAgICBjb3VudHJ5X2NvZGUgJWluJSBjKGNvdW50cnlfY29kZXMpKSwNCiAgYWVzKHggPSBhdmVyYWdlX21vYmlsaXR5LCB5ID0gUnRfbWVhbikpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyPWNvdW50cnkpKSArDQogIGdndGl0bGUoIlJlcHJvZHVjdGl2ZSBudW1iZXIgYnkgbW9iaWxpdHkiKSArDQogIHlsYWIoZXhwcmVzc2lvbihSW3QsbV1eY2FzZXMpKSArDQogICNzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxMCkpICsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobmNvbCA9IDEpKSArDQogICAgc2NhbGVfY29sb3JfaHVlKGwgPSA1MCkNCmBgYA0KDQojIyMgTW9kZWxsaW5nIFJlc3VsdHMNCg0KKioqVE8gRE8qKioNCg0KIyBEaXNjdXNzaW9uDQoNCkZyb20gdGhlIGJhc2ljIHBsb3RzIGl0J3MgY2xlYXIgdGhhdCBTb3V0aCBBZnJpY2EgYW5kIGEgZmV3IG90aGVyIGNvdW50cmllcyBhcHBlYXIgdG8gYmUgb24gYSBkaWZmZXJlbnQgInNsb3BlIiB0aGFuIEV1cm9wZWFuIGNvdW50cmllcyBzaG93LiAgVGhlc2UgaW5jbHVkZSBCcmF6aWwsIFBlcnUsIENoaWxlIGFuZCBJbmRpYS4NCg0KVGhlIGFib3ZlIHNob3dzIGVzdGltYXRlcyBmb3IgcmVwcm9kdWN0aXZlIG51bWJlciB1c2luZyBjYXNlcyBhbmQgZGVhdGhzICgkUl97dCxtfV57Y2FzZXN9JCBhbmQgJFJfe3QsbX1ee2RlYXRoc30kKSBmb3IgZWFjaCBjb3VudHJ5ICRtJCBvdmVyIHRpbWUgJHQkIGluIHdlZWtzLg0KDQpGcm9tIHRoZSBjdXJyZW50IGVzdGltYXRlcyBpdCBhcHBlYXJzIHRoYXQsIGF0IHByZXNlbnQsIGluIGdlbmVyYWwgU291dGggQWZyaWNhbiBwcm92aW5jaWFsIGVzdGltYXRlcyBiYXNlZCBvbiBjYXNlcyBhbmQgZGVhdGhzIGNvcnJlc3BvbmQuICBBbiBleGNlcHRpb24gdG8gdGhpcyBtYXkgYmUgRWFzdGVybiBDYXBlIHdpdGggZXN0aW1hdGVzIGJhc2VkIG9uIGRlYXRocyBleGNlZWRpbmcgdGhvc2UgYmFzZWQgb24gY2FzZXMuDQoNCkl0IGlzIGFsc28gY2xlYXIgdGhhdCBlc3RpbWF0ZXMgYmFzZWQgb24gY2FzZXMgYXBwZWFyIHRvIHByZWNlZGUgdGhlIG1vdmVtZW50cyBiYXNlZCBvbiBkZWF0aHMgb3ZlciB0aW1lLiAgVGhpcyBjYW4gYmUgZXhwZWN0ZWQgYXMsIGFsbCB0aGluZ3MgYmVpbmcgZXF1YWwsIGluZmVjdGlvbnMgYW5kIGFzc29jaWF0ZWQgY2FzZXMgc2hvdWxkIGJlIGEgcHJlY3Vyc29yIHRvIGRlYXRocy4NCg0KU291dGggQWZyaWNhIGRvZXMgbm90IGNvbXBhcmUgd2VsbCwgYW5kIGFwcGVhcnMgdG8gaGF2ZSBvbmUgb2YgdGhlIGhpZ2hlc3QgJFJfe3QsbX1ee2RlYXRoc30kIGluIHRoZSB3b3JsZC4gIFRoaXMgaXMgaGlnaGVyIHRoYW4gdGhlICRSX3t0LG19XntjYXNlc30kIHdoaWNoIG1heSBiZSBpbmRpY2F0aXZlIG9mIGxvd2VyIHRlc3RpbmcgYW5kL29yIGJhY2tsb2dzIHdpdGggcmVnYXJkIHRvIHRlc3RpbmcuDQoNCk9uIGEgc2NhdHRlciBwbG90IG9mIGNvdW50cmllcyBtb3N0IGFwcGVhciB0byBoYXZlICRSX3t0LG19XntjYXNlc30kIGNvcnJlbGF0ZWQgd2VsbCB3aXRoICRSX3t0LG19XntkZWF0aHN9JC4gIENoaWxlIGFuZCBBZmdoYW5pc3RhbiBhcHBlYXIgdG8gYmUgb3V0bGllcnMgd2l0aCAkUl97dCxtfV57ZGVhdGhzfSQgaGlnaGVyIHRoYW4gJFJfe3QsbX1ee2Nhc2VzfSQgaW5kaWNhdGluZyBwZXJoYXBzIGxpbWl0ZWQgdGVzdGluZywgb3IgYWx0ZXJuYXRpdmVseSBlcGlkZW1pY3MgdGhhdCBhcmUgc2xvd2luZyAoZ2l2ZW4gdGhlIGxlYWRpbmcgbmF0dXJlIG9mIGNhc2VzIHZzLiBkZWF0aHMpLg0KDQpPdmVyYWxsIGl0J3MgY2xlYXIgdGhhdCBoYXZpbmcgbXVsdGlwbGUgbWVhc3VyZXMgb2YgJFJfe3QsbX0kIGFwcGVhcnMgdXNlZnVsIGFuZCBtb25pdG9yaW5nIGl0J3MgdmFsdWUgaXMgc29tZXRoaW5nIHVzZWZ1bC4NCg0KV2UgYWxzbyBzaG93IHRoYXQgbW9iaWxpdHkgaXMgY2xlYXJseSBjb3JyZWxhdGVkIHdpdGggJFJfe3QsbX1ee2Nhc2VzfSQgaW4gdGhlIGNvdW50cmllcyBzaG93bi4gIA0KDQpMaW1pdGF0aW9uIG9mIHRoaXMgbWV0aG9kIHRvIGVzdGltYXRlICRSX3t0LG19JCBhcmUgbm90ZWQgaW4gW0BDb3JpMjAxM10NCg0KKiBJdCdzIHNlbnNpdGl2ZSB0byBjaGFuZ2VzIGluIHRyYW5zbWlzc2liaWxpdHksIGNoYW5nZXMgaW4gY29udGFjdCBwYXR0ZXJucywgZGVwbGV0aW9uIG9mIHRoZSBzdXNjZXB0aWJsZSBwb3B1bGF0aW9uIGFuZCBjb250cm9sIG1lYXN1cmVzLg0KKiBJdCByZWxpZXMgb24gYW4gYXNzdW1lZCBzZXJpYWwgaW50ZXJ2YWwgYXNzdW1wdGlvbnMuDQoqIFRoZSBzaXplIG9mIHRoZSB0aW1lIHdpbmRvdyBjYW4gYWZmZWN0IHRoZSB2b2xhdGlsaXR5IG9mIHJlc3VsdHMuDQoqIFJlc3VsdHMgYXJlIHRpbWUgbGFnZ2VkIHdpdGggcmVnYXJkcyB0byB0cnVlIGluZmVjdGlvbiwgbW9yZSBzbyBpbiB0aGUgY2FzZSBvZiB0aGUgdXNlIG9mIGRlYXRocy4NCiogSXQncyBzZW5zaXRpdmUgdG8gY2hhbmdlcyBpbiBjYXNlIChvciBkZWF0aCkgZGV0ZWN0aW9uLg0KKiBUaGUgc2VyaWFsIGludGVydmFsIG1heSBjaGFuZ2Ugb3ZlciB0aW1lLg0KDQpEZXNwaXRlIHRoZXNlIGxpbWl0YXRpb24gd2UgYmVsaWV2ZSB0aGUgZWFzZSBvZiBjYWxjdWxhdGlvbiBvZiB0aGlzIG1ldGhvZCBhbmQgdGhlIGFiaWxpdHkgdG8gdXNlIG11bHRpcGxlIHNvdXJjZXMgbWFrZXMgaXQgdXNlZnVsIGFzIGEgbW9uaXRvcmluZyB0b29sLg0KDQojIERldGFpbGVkIE91dHB1dA0KDQpEZXRhaWxlZCBvdXRwdXQgZm9yIGFsbCBjb3VudHJpZXMgYXJlIHNhdmVkIHRvIGEgY29tbWEtc2VwYXJhdGVkIHZhbHVlIGZpbGUgYmVsb3cuICBUaGUgZmlsZSBbY2FuIGJlIGZvdW5kIGhlcmVdKFJ0X2RhdGEuY3N2KS4NCg0KYGBge3Igd3JpdGVfY3N2fQ0Kd3JpdGUuY3N2KFJ0X2RhdGEsIlJ0X2RhdGEuY3N2Iixyb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQojIEF1dGhvcg0KDQpUaGlzIHJlcG9ydCB3YXMgcHJlcGFyZWQgYnkgTG91aXMgUm9zc291dy4gIFBsZWFzZSBnZXQgaW4gY29udGFjdCB3aXRoIExvdWlzIFJvc3NvdXcgaWYgeW91IGhhdmUgY29tbWVudHMgb3Igd2lzaCB0byByZWNlaXZlIHRoaXMgcmVndWxhcmx5Lg0KDQoqKkxvdWlzIFJvc3NvdXcqKiAgDQpIZWFkIG9mIFJlc2VhcmNoICYgQW5hbHl0aWNzICANCkdlbiBSZSB8IExpZmUvSGVhbHRoIENhbmFkYSwgU291dGggQWZyaWNhLCBBdXN0cmFsaWEsIE5aLCBVSyAmIElyZWxhbmQgIA0KRW1haWw6IFtMUm9zc291d0BHZW5SZS5jb21dKG1haWx0bzpMUm9zc291d0BnZW5yZS5jb20pIE1vYmlsZTogKzI3IDcxIDM1NSAyNTUwDQoNCioqKlRoZSB2aWV3cyBpbiB0aGlzIGRvY3VtZW50IHJlcHJlc2VudHMgdGhhdCBvZiB0aGUgYXV0aG9yIGFuZCBtYXkgbm90IHJlcHJlc2VudCB0aG9zZSBvZiBHZW4gUmUuICBBbHNvIG5vdGUgdGhhdCBnaXZlbiB0aGUgc2lnbmlmaWNhbnQgdW5jZXJ0YWludHkgaW52b2x2ZWQgd2l0aCB0aGUgcGFyYW1ldGVycywgZGF0YSBhbmQgbWV0aG9kb2xvZ3kgY2FyZSBzaG91bGQgYmUgdGFrZW4gd2l0aCB0aGVzZSBudW1iZXJzIGFuZCBhbnkgdXNlIG9mIHRoZXNlIG51bWJlcnMuKioqDQoNCiMgUmVmZXJlbmNlcw0K